diff --git a/bin/convert_caffe.py b/bin/convert_caffe.py index af8e4df7b..9a9bce582 100644 --- a/bin/convert_caffe.py +++ b/bin/convert_caffe.py @@ -3,7 +3,6 @@ """ import argparse -import ast import os import sys from os import path @@ -14,8 +13,10 @@ import numpy as np from webdnn.backend.interface.generator import generate_descriptor -from webdnn.graph.converters.chainer import ChainerGraphConverter +from webdnn.frontend.chainer import ChainerConverter from webdnn.graph.graph import Graph +from webdnn.graph.shape import Shape +from webdnn.util import console def parse_input_blob(args): @@ -28,7 +29,9 @@ def parse_input_blob(args): else: if not args.input_shape: raise ValueError("input_npy or input_shapes must be specified to determine input") - input_shape = ast.literal_eval(args.input_shape) + input_shape, placeholders = Shape.parse(args.input_shape) + if len(placeholders) > 0: + raise ValueError("caffe converter does not support an input with placeholder") input_blob = chainer.Variable(np.zeros(input_shape, dtype=np.float32)) return input_blob, input_filled @@ -57,14 +60,19 @@ def main(): input_blob, input_filled = parse_input_blob(args) output_names = args.output_names.split(",") - sys.stderr.write("Loading caffe model... (usually takes several minutes)\n") + console.stderr("[convert_caffe] Loading caffe model... (usually takes several minutes)") link = chainer.links.caffe.CaffeFunction(args.caffemodel) - sys.stderr.write("Generating feedforward graph\n") - output_blobs = list( - link(inputs={args.input_name: input_blob}, outputs=output_names, train=False)) # list of Variable + console.stderr("[convert_caffe] Generating feedforward graph") + if chainer.__version__ >= "2.": + chainer.using_config("train", False) + output_blobs = list( + link(inputs={args.input_name: input_blob}, outputs=output_names)) # list of Variable + else: + output_blobs = list( + link(inputs={args.input_name: input_blob}, outputs=output_names, train=False)) # list of Variable chainer_cg = chainer.computational_graph.build_computational_graph(output_blobs) - converter = ChainerGraphConverter() + converter = ChainerConverter() graph = converter.convert(chainer_cg, [input_blob], output_blobs) # type: Graph if args.out: @@ -78,7 +86,7 @@ def main(): output_arrays = {output_name: output_blob.data for output_name, output_blob in zip(output_names, output_blobs)} np.savez(path.join(output_dir, "example_output.npz"), **output_arrays) - sys.stderr.write("Generating descriptors\n") + console.stderr("[convert_caffe] Generating descriptors") any_backend_failed = False for backend in args.backend.split(","): try: @@ -86,7 +94,7 @@ def main(): graph_exec_data.save(output_dir) except Exception as ex: any_backend_failed = True - sys.stderr.write(f"Failed generating descriptor for backend {backend}: {str(ex)}\n") + console.error(f"[convert_caffe] Failed generating descriptor for backend {backend}: {str(ex)}") if any_backend_failed: sys.exit(1) diff --git a/bin/convert_keras.py b/bin/convert_keras.py index 2dc8b0101..e866deb87 100644 --- a/bin/convert_keras.py +++ b/bin/convert_keras.py @@ -3,16 +3,25 @@ """ import argparse -import ast +import importlib.util import os import sys +import traceback from os import path import h5py from webdnn.backend.interface.generator import generate_descriptor -from webdnn.graph.converters.keras import KerasGraphConverter -from webdnn.graph.graph import Graph +from webdnn.frontend.keras import KerasConverter +from webdnn.graph.shape import Shape +from webdnn.graph.traverse import dump_dot +from webdnn.util import flags, console + + +def _load_plugin(filepath: str): + spec = importlib.util.spec_from_file_location("_plugin", filepath) + plugin = importlib.util.module_from_spec(spec) + spec.loader.exec_module(plugin) def main(): @@ -27,13 +36,19 @@ def main(): parser.add_argument("--out", help="output directory (default: /webdnn_graph_descriptor)") parser.add_argument("--encoding", help="name of weight encoder") + parser.add_argument("--visualize_ir", action="store_true") + parser.add_argument("--plugin", action="append", help="plugin python files which are imported before transpiling") args = parser.parse_args() - sys.stderr.write("Generating feedforward graph\n") - input_shape = ast.literal_eval(args.input_shape) + console.stderr(f"[{path.basename(__file__)}] Generating feedforward graph") + if args.plugin: + for plugin_path in args.plugin: + _load_plugin(plugin_path) + + input_shape, _ = Shape.parse(args.input_shape) input_shapes = [input_shape] model = h5py.File(args.kerasmodel, "r") - converter = KerasGraphConverter() + converter = KerasConverter() graph = converter.convert(model, input_shapes) if args.out: @@ -42,20 +57,33 @@ def main(): output_dir = path.join(path.dirname(args.kerasmodel), "webdnn_graph_descriptor") os.makedirs(output_dir, exist_ok=True) - sys.stderr.write("Generating descriptors\n") + if args.visualize_ir: + ir_dot_path = path.join(output_dir, "ir.dot") + with open(ir_dot_path, "w") as f: + f.write(dump_dot(graph)) + console.stderr(f"IR graph can be visualized with graphviz command: 'dot {ir_dot_path} -T png -o output.png'") + + console.stderr(f"[{path.basename(__file__)}] Generating graph descriptor") + any_backend_failed = False - last_backend_exception = None - for backend in args.backend.split(","): + backends = args.backend.split(",") + for i, backend in enumerate(backends): + console.stderr(f"[{path.basename(__file__)}] Backend: {console.colorize(backend, console.Color.Cyan)}") try: graph_exec_data = generate_descriptor(backend, graph, constant_encoder_name=args.encoding) graph_exec_data.save(output_dir) except Exception as ex: + if flags.DEBUG: + raise ex + any_backend_failed = True - last_backend_exception = ex - sys.stderr.write(f"Failed generating descriptor for backend {backend}: {str(ex)}\n") + console.error(f"[{path.basename(__file__)}] Failed generating descriptor for {backend} backend") + console.stderr(traceback.format_exc()) + continue if any_backend_failed: - raise last_backend_exception + exit(1) + # raise last_backend_exception if __name__ == "__main__": diff --git a/dist/webdnn-1.0.0-py3.6.egg b/dist/webdnn-1.0.0-py3.6.egg deleted file mode 100644 index ec740b1f3..000000000 Binary files a/dist/webdnn-1.0.0-py3.6.egg and /dev/null differ diff --git a/dist/webdnn-1.1.0-py3.6.egg b/dist/webdnn-1.1.0-py3.6.egg new file mode 100644 index 000000000..007a38340 Binary files /dev/null and b/dist/webdnn-1.1.0-py3.6.egg differ diff --git a/dist/webdnn.d.ts b/dist/webdnn.d.ts index 9af45816d..81a80700a 100644 --- a/dist/webdnn.d.ts +++ b/dist/webdnn.d.ts @@ -12,33 +12,134 @@ declare namespace WebDNN { */ outputs: string[]; /** - * Allocation information for each variable. + * memory position table */ - weight_allocation: { - allocation: { - [name: string]: any; - }; - }; + memory_layout: MemoryLayout; /** - * Allocation information for each variable. + * Encoding algorithm of weight binary data. */ - variable_allocation: { - allocation: { - [name: string]: any; + weight_encoding: string; + /** + * Placeholder dict + */ + placeholders: { + [key: string]: number; + }; + } +} +declare namespace WebDNN { + type Placeholder = { + eval: string; + }; + /** + * PlaceholderContext manages the placeholders + */ + class PlaceholderContext { + private values; + constructor(values?: { + [key: string]: number | null; + }); + readonly isResolved: boolean; + update(values: { + [key: string]: number | null; + }): void; + resolve(placeholder: any): any; + toString(): string; + } +} +declare namespace WebDNN { + interface Allocation { + name: string; + offset: number | Placeholder; + size: number | Placeholder; + } + interface ResolvedAllocation extends Allocation { + offset: number; + size: number; + } + interface MemoryLayout { + 'static': { + size: number; + allocations: { + [index: string]: ResolvedAllocation; + }; + }; + dynamic: { + size: number | Placeholder; + allocations: { + [index: string]: Allocation; }; }; + } +} +declare namespace WebDNN { + abstract class SymbolicArrayBufferView { + protected ignoreOffsetOnActual: boolean; + protected arrayBuffer?: ArrayBuffer; + protected allocation: Allocation; + protected placeholderContext?: PlaceholderContext; /** - * Encoding algorithm of weight binary data. + * Convert symbolic buffer view into actual buffer view. + * If this buffer view is initialized based on placeholder offset or size and the placeholder is not resolved, + * the error is thrown. */ - weight_encoding: string; + abstract toActual(): T; + constructor(allocation: Allocation, placeholderContext?: PlaceholderContext, ignoreOffsetOnActual?: boolean); + setArrayBuffer(arrayBuffer: any): void; + readonly isDynamic: boolean; + readonly offset: any; + readonly length: any; + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + } + class SymbolicFloat32Array extends SymbolicArrayBufferView { + toActual(): Float32Array; + } + class SymbolicInt32Array extends SymbolicArrayBufferView { + toActual(): Int32Array; } } declare namespace WebDNN { /** * `DescriptorRunner` executes computation based on `GraphDescriptor`. + * + * Typically, DescriptorRunner takes 3 steps to execute DNN model. + * + * 1. Initialize static configurations + * + * Initialize things independent from runtime configuration. + * + * - `init()` + * - `load()` + * + * 2. Initialize dynamic configurations + * + * Initialize things depend on runtime configuration such as batch size, input image size, etc. + * + * - `setPlaceholderValue()` + * - `getInputViews()` + * - `getOutputViews()` + * + * 3. Execute the model + * + * - `run()` + * + * You need to do step 1 and 2 only once. We recommend to call `WebDNN.prepareAll()` instead + * to call `GraphDescriptor#load()` directly. In that method, all procedures in step 1 and 2 are performed. */ - interface DescriptorRunner { - backend: string; + abstract class DescriptorRunner { + readonly backendName: string; + descriptor: D | null; + placeholderContext: PlaceholderContext | null; + ignoreCache: boolean; + /** + * Initialize this runner + */ + abstract init(): Promise; /** * Fetch descriptor from specified directory. * @param directory directory where descriptor is contained. @@ -52,39 +153,25 @@ declare namespace WebDNN { * * @param progressCallback callback which is called to notice the loading is progressing. */ - load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; + abstract load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; /** - * set descriptor. - * @param descriptor descriptor which will be executed. + * Set actual value into placeholders. If no placeholder is exist in graph descriptor, it's no need to call this function. */ - setDescriptor(descriptor: GraphDescriptor): void; + abstract setPlaceholderValue(values: { + [key: string]: number; + }): Promise; /** - * compile kernels. + * Get input ArrayBufferView object */ - compile(): Promise; + abstract getInputViews(): SymbolicFloat32Array[]; /** - * load weight data - * @param weightsData weights data + * Get output ArrayBufferView object */ - loadWeights(weightsData: Uint8Array): Promise; + abstract getOutputViews(): SymbolicFloat32Array[]; /** * Run descriptor. You must call [[getInputViews]] and [[getOutputViews]] before calling this function. */ - run(): Promise; - /** - * Get input ArrayBufferView object - */ - getInputViews(): Promise; - /** - * Get output ArrayBufferView object - */ - getOutputViews(): Promise; - } -} -declare namespace WebDNN { - interface GPUInterface { - init(): Promise; - createDescriptorRunner(): DescriptorRunner; + abstract run(): Promise; } } declare namespace WebDNN { @@ -202,31 +289,24 @@ interface WebGPUComputeCommandEncoder extends WebGPUCommandEncoder { } interface WebGPUComputePipelineState { } -declare namespace WebDNN { - class WeightDecoderRaw implements WeightDecoder { - decode(data: Uint8Array, weight_allocation: WeightAllocation): Promise; - } +interface HTMLCanvasElement { + getContext(contextId: "webgpu"): WebGPURenderingContext | null; } declare namespace WebDNN { interface WeightDecoder { - decode(data: Uint8Array, weight_allocation: WeightAllocation): Promise; + decode(data: Uint8Array, memory_layout: MemoryLayout): Promise; } - interface WeightAllocation { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; +} +declare namespace WebDNN { + class WeightDecoderRaw implements WeightDecoder { + decode(data: Uint8Array, memory_layout: MemoryLayout): Promise; } } declare var Zlib: any; declare namespace WebDNN { class WeightDecoderEightbit implements WeightDecoder { static decode_table: number[]; - decode(data: Uint8Array, weight_allocation: WeightAllocation): Promise; + decode(data: Uint8Array, memory_layout: MemoryLayout): Promise; } } declare namespace WebDNN { @@ -266,6 +346,9 @@ declare let transformDelegate: (base: string) => string; */ declare let fetchDelegate: (input: RequestInfo, init?: RequestInit) => Promise; declare namespace WebDNN { + interface WebDNNRequestInit extends RequestInit { + ignoreCache: boolean; + } /** * Register delegate function for transform url * @param url url which will be transformed @@ -285,9 +368,10 @@ declare namespace WebDNN { * Fetch function. WebDNN API use this fetch function instead of original fetch function. * @param input Requested url * @param init Additional information about fetch + * @param init.ignoreCache If true, cache is ignored by appending '?t=(timestamp)' to the end of request url. * @returns Response */ - function fetch(input: RequestInfo, init?: RequestInit): Promise; + function fetch(input: RequestInfo, init?: WebDNNRequestInit): Promise; /** * Read `Response.body` stream as ArrayBuffer. This function provide progress information by callback. * @param res Response object @@ -298,26 +382,7 @@ declare namespace WebDNN { } declare namespace WebDNN { interface GraphDescriptorWebGPU extends GraphDescriptor { - weight_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; - variable_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; + memory_layout: MemoryLayout; kernel_source: string; exec_infos: GraphDescriptorWebGPUExecInfos[]; } @@ -326,117 +391,76 @@ declare namespace WebDNN { threadgroups_per_grid: WebGPUSize; threads_per_thread_group: WebGPUSize; meta_buffer: number[]; + unresolved_value_list: { + offset: number; + placeholder: Placeholder; + }[]; } } +declare const IS_IOS: boolean; declare namespace WebDNN { - class DescriptorRunnerWebGPU implements DescriptorRunner { - private webGPUHandler; - private descriptor; - private weightMat; - private dataMat; - private metaBufferGPUBuffers; - ignoreCache: boolean; - backend: string; + class DescriptorRunnerWebGPU extends DescriptorRunner { + readonly backendName: string; + private webgpuHandler; + private shaderLanguage; + private staticBuffer; + private dynamicBuffer; + private metaBuffers; private inputViews; private outputViews; - constructor(webGPUHandler: WebGPUHandler); - load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; - setDescriptor(descriptor: GraphDescriptorWebGPU): void; - compile(): Promise; - loadWeights(weightsData: Uint8Array): Promise; - getInputViews(): Promise; - getOutputViews(): Promise; - run(): Promise; - } -} -declare let WebGPUComputeCommandEncoder: any; -declare namespace WebDNN { - class GPUInterfaceWebGPU implements GPUInterface { - private option; - webgpuHandler: WebGPUHandler; - shaderLanguage: string; + private executionInfos; constructor(option?: any); init(): Promise; - private init_basic_kernels(); - createDescriptorRunner(): DescriptorRunner; + private initializeBasicKernels(); + load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; + private initializeStaticBuffer(weightRawArray); + private initializeMetaBuffers(); + private initializeDynamicBuffer(); + private setDescriptor(descriptor); + private compile(); + setPlaceholderValue(values: { + [key: string]: number; + }): Promise; + getInputViews(): SymbolicFloat32Array[]; + getOutputViews(): SymbolicFloat32Array[]; + run(): Promise; } } declare namespace WebDNN { interface GraphDescriptorWebassembly extends GraphDescriptor { - weight_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; - variable_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; + unresolved_value_lists: { + offset: number; + placeholder: Placeholder; + }[][]; } } +declare let WebAssembly: any; declare namespace WebDNN { - class DescriptorRunnerWebassembly implements DescriptorRunner { + class DescriptorRunnerWebassembly extends DescriptorRunner { + readonly backendName: string; private inputViews; private outputViews; private worker; - descriptor: GraphDescriptorWebassembly; - ignoreCache: boolean; - backend: string; private worker_entry_js_path; private worker_promise_reject_func; private worker_initial_error; - constructor(); - load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; - setDescriptor(descriptor: GraphDescriptorWebassembly): void; - compile(): Promise; - loadWeights(weightsData: Uint8Array): Promise; - getInputViews(): Promise; - getOutputViews(): Promise; - run(): Promise; - } -} -declare var WebAssembly: any; -declare namespace WebDNN { - class GPUInterfaceWebassembly implements GPUInterface { - private option; constructor(option?: any); init(): Promise; - createDescriptorRunner(): DescriptorRunner; + load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; + setPlaceholderValue(values: { + [key: string]: number; + }): Promise; + private setPlaceholderValueWorker(dynamicBufferSize, metaBufferFillArray); + private compile(); + private loadWeights(weightsData); + getInputViews(): SymbolicFloat32Array[]; + getOutputViews(): SymbolicFloat32Array[]; + run(): Promise; } } declare namespace WebDNN { interface GraphDescriptorFallback extends GraphDescriptor { - weight_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; - variable_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; + memory_layout: MemoryLayout; kernel_source: string; exec_infos: GraphDescriptorFallbackExecInfo[]; } @@ -448,42 +472,38 @@ declare namespace WebDNN { call_option: any; } } +declare function wait(duration?: number): Promise<{}>; declare namespace WebDNN { - class DescriptorRunnerFallback implements DescriptorRunner { - descriptor: GraphDescriptorFallback; - kernelObj: any; - rawWeightArray: Float32Array; - weightArrays: Map; - variableArrays: Map; - ignoreCache: boolean; - backend: string; + class DescriptorRunnerFallback extends DescriptorRunner { + readonly backendName: string; + private kernelObj; + private variableMap; private inputViews; private outputViews; - constructor(); + private staticBuffer; + private dynamicBuffer; + constructor(option?: any); + init(): Promise; load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; - setDescriptor(descriptor: GraphDescriptorFallback): void; - compile(): Promise; - private compileKernel(); - loadWeights(weightsData: Uint8Array): Promise; + private setDescriptor(descriptor); + private compile(); + private initializeStaticBuffer(weightRawArray); + private initializeDynamicBuffer(); + setPlaceholderValue(values: { + [key: string]: number; + }): Promise; run(): Promise; - wait_to_display(): Promise<{}>; - getInputViews(): Promise; - getOutputViews(): Promise; - } -} -declare namespace WebDNN { - class GPUInterfaceFallback implements GPUInterface { - private option; - constructor(option?: any); - init(option?: any): Promise; - createDescriptorRunner(): DescriptorRunner; + getInputViews(): SymbolicFloat32Array[]; + getOutputViews(): SymbolicFloat32Array[]; } } declare namespace WebDNN { - let gpu: GPUInterface; - function init(backendOrder?: string | string[], backendOptions?: { - [key: string]: any; - }): Promise; + const backends: { + 'webgpu': typeof DescriptorRunnerWebGPU; + 'webassembly': typeof DescriptorRunnerWebassembly; + 'fallback': typeof DescriptorRunnerFallback; + }; + let DEBUG: boolean; /** * Prepare backend interface and load model data at once. Internally calls init(). * @param backendOrder The trying order of backend names to be initialized. @@ -495,6 +515,7 @@ declare namespace WebDNN { backendOptions?: { [key: string]: any; }; + ignoreCache?: boolean; progressCallback?: (loaded: number, total: number) => any; } /** @@ -503,28 +524,7 @@ declare namespace WebDNN { * @param initOption Initialize option * @return Interface to input/output data and run the model. */ - function prepareAll(directory: string, initOption?: InitOption): Promise; - /** - * Interface to input/output data and run the model. - */ - interface GraphInterface { - /** - * The name of backend. - */ - backendName: string; - /** - * The buffers to write input data. - */ - inputViews: Float32Array[]; - /** - * The buffers to read output data. - */ - outputViews: Float32Array[]; - /** - * Run the model. - */ - run: () => Promise; - } + function load(directory: string, initOption?: InitOption): Promise>; } declare namespace WebDNN { namespace Math { diff --git a/dist/webdnn.es5.d.ts b/dist/webdnn.es5.d.ts index 9af45816d..81a80700a 100644 --- a/dist/webdnn.es5.d.ts +++ b/dist/webdnn.es5.d.ts @@ -12,33 +12,134 @@ declare namespace WebDNN { */ outputs: string[]; /** - * Allocation information for each variable. + * memory position table */ - weight_allocation: { - allocation: { - [name: string]: any; - }; - }; + memory_layout: MemoryLayout; /** - * Allocation information for each variable. + * Encoding algorithm of weight binary data. */ - variable_allocation: { - allocation: { - [name: string]: any; + weight_encoding: string; + /** + * Placeholder dict + */ + placeholders: { + [key: string]: number; + }; + } +} +declare namespace WebDNN { + type Placeholder = { + eval: string; + }; + /** + * PlaceholderContext manages the placeholders + */ + class PlaceholderContext { + private values; + constructor(values?: { + [key: string]: number | null; + }); + readonly isResolved: boolean; + update(values: { + [key: string]: number | null; + }): void; + resolve(placeholder: any): any; + toString(): string; + } +} +declare namespace WebDNN { + interface Allocation { + name: string; + offset: number | Placeholder; + size: number | Placeholder; + } + interface ResolvedAllocation extends Allocation { + offset: number; + size: number; + } + interface MemoryLayout { + 'static': { + size: number; + allocations: { + [index: string]: ResolvedAllocation; + }; + }; + dynamic: { + size: number | Placeholder; + allocations: { + [index: string]: Allocation; }; }; + } +} +declare namespace WebDNN { + abstract class SymbolicArrayBufferView { + protected ignoreOffsetOnActual: boolean; + protected arrayBuffer?: ArrayBuffer; + protected allocation: Allocation; + protected placeholderContext?: PlaceholderContext; /** - * Encoding algorithm of weight binary data. + * Convert symbolic buffer view into actual buffer view. + * If this buffer view is initialized based on placeholder offset or size and the placeholder is not resolved, + * the error is thrown. */ - weight_encoding: string; + abstract toActual(): T; + constructor(allocation: Allocation, placeholderContext?: PlaceholderContext, ignoreOffsetOnActual?: boolean); + setArrayBuffer(arrayBuffer: any): void; + readonly isDynamic: boolean; + readonly offset: any; + readonly length: any; + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + } + class SymbolicFloat32Array extends SymbolicArrayBufferView { + toActual(): Float32Array; + } + class SymbolicInt32Array extends SymbolicArrayBufferView { + toActual(): Int32Array; } } declare namespace WebDNN { /** * `DescriptorRunner` executes computation based on `GraphDescriptor`. + * + * Typically, DescriptorRunner takes 3 steps to execute DNN model. + * + * 1. Initialize static configurations + * + * Initialize things independent from runtime configuration. + * + * - `init()` + * - `load()` + * + * 2. Initialize dynamic configurations + * + * Initialize things depend on runtime configuration such as batch size, input image size, etc. + * + * - `setPlaceholderValue()` + * - `getInputViews()` + * - `getOutputViews()` + * + * 3. Execute the model + * + * - `run()` + * + * You need to do step 1 and 2 only once. We recommend to call `WebDNN.prepareAll()` instead + * to call `GraphDescriptor#load()` directly. In that method, all procedures in step 1 and 2 are performed. */ - interface DescriptorRunner { - backend: string; + abstract class DescriptorRunner { + readonly backendName: string; + descriptor: D | null; + placeholderContext: PlaceholderContext | null; + ignoreCache: boolean; + /** + * Initialize this runner + */ + abstract init(): Promise; /** * Fetch descriptor from specified directory. * @param directory directory where descriptor is contained. @@ -52,39 +153,25 @@ declare namespace WebDNN { * * @param progressCallback callback which is called to notice the loading is progressing. */ - load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; + abstract load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; /** - * set descriptor. - * @param descriptor descriptor which will be executed. + * Set actual value into placeholders. If no placeholder is exist in graph descriptor, it's no need to call this function. */ - setDescriptor(descriptor: GraphDescriptor): void; + abstract setPlaceholderValue(values: { + [key: string]: number; + }): Promise; /** - * compile kernels. + * Get input ArrayBufferView object */ - compile(): Promise; + abstract getInputViews(): SymbolicFloat32Array[]; /** - * load weight data - * @param weightsData weights data + * Get output ArrayBufferView object */ - loadWeights(weightsData: Uint8Array): Promise; + abstract getOutputViews(): SymbolicFloat32Array[]; /** * Run descriptor. You must call [[getInputViews]] and [[getOutputViews]] before calling this function. */ - run(): Promise; - /** - * Get input ArrayBufferView object - */ - getInputViews(): Promise; - /** - * Get output ArrayBufferView object - */ - getOutputViews(): Promise; - } -} -declare namespace WebDNN { - interface GPUInterface { - init(): Promise; - createDescriptorRunner(): DescriptorRunner; + abstract run(): Promise; } } declare namespace WebDNN { @@ -202,31 +289,24 @@ interface WebGPUComputeCommandEncoder extends WebGPUCommandEncoder { } interface WebGPUComputePipelineState { } -declare namespace WebDNN { - class WeightDecoderRaw implements WeightDecoder { - decode(data: Uint8Array, weight_allocation: WeightAllocation): Promise; - } +interface HTMLCanvasElement { + getContext(contextId: "webgpu"): WebGPURenderingContext | null; } declare namespace WebDNN { interface WeightDecoder { - decode(data: Uint8Array, weight_allocation: WeightAllocation): Promise; + decode(data: Uint8Array, memory_layout: MemoryLayout): Promise; } - interface WeightAllocation { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; +} +declare namespace WebDNN { + class WeightDecoderRaw implements WeightDecoder { + decode(data: Uint8Array, memory_layout: MemoryLayout): Promise; } } declare var Zlib: any; declare namespace WebDNN { class WeightDecoderEightbit implements WeightDecoder { static decode_table: number[]; - decode(data: Uint8Array, weight_allocation: WeightAllocation): Promise; + decode(data: Uint8Array, memory_layout: MemoryLayout): Promise; } } declare namespace WebDNN { @@ -266,6 +346,9 @@ declare let transformDelegate: (base: string) => string; */ declare let fetchDelegate: (input: RequestInfo, init?: RequestInit) => Promise; declare namespace WebDNN { + interface WebDNNRequestInit extends RequestInit { + ignoreCache: boolean; + } /** * Register delegate function for transform url * @param url url which will be transformed @@ -285,9 +368,10 @@ declare namespace WebDNN { * Fetch function. WebDNN API use this fetch function instead of original fetch function. * @param input Requested url * @param init Additional information about fetch + * @param init.ignoreCache If true, cache is ignored by appending '?t=(timestamp)' to the end of request url. * @returns Response */ - function fetch(input: RequestInfo, init?: RequestInit): Promise; + function fetch(input: RequestInfo, init?: WebDNNRequestInit): Promise; /** * Read `Response.body` stream as ArrayBuffer. This function provide progress information by callback. * @param res Response object @@ -298,26 +382,7 @@ declare namespace WebDNN { } declare namespace WebDNN { interface GraphDescriptorWebGPU extends GraphDescriptor { - weight_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; - variable_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; + memory_layout: MemoryLayout; kernel_source: string; exec_infos: GraphDescriptorWebGPUExecInfos[]; } @@ -326,117 +391,76 @@ declare namespace WebDNN { threadgroups_per_grid: WebGPUSize; threads_per_thread_group: WebGPUSize; meta_buffer: number[]; + unresolved_value_list: { + offset: number; + placeholder: Placeholder; + }[]; } } +declare const IS_IOS: boolean; declare namespace WebDNN { - class DescriptorRunnerWebGPU implements DescriptorRunner { - private webGPUHandler; - private descriptor; - private weightMat; - private dataMat; - private metaBufferGPUBuffers; - ignoreCache: boolean; - backend: string; + class DescriptorRunnerWebGPU extends DescriptorRunner { + readonly backendName: string; + private webgpuHandler; + private shaderLanguage; + private staticBuffer; + private dynamicBuffer; + private metaBuffers; private inputViews; private outputViews; - constructor(webGPUHandler: WebGPUHandler); - load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; - setDescriptor(descriptor: GraphDescriptorWebGPU): void; - compile(): Promise; - loadWeights(weightsData: Uint8Array): Promise; - getInputViews(): Promise; - getOutputViews(): Promise; - run(): Promise; - } -} -declare let WebGPUComputeCommandEncoder: any; -declare namespace WebDNN { - class GPUInterfaceWebGPU implements GPUInterface { - private option; - webgpuHandler: WebGPUHandler; - shaderLanguage: string; + private executionInfos; constructor(option?: any); init(): Promise; - private init_basic_kernels(); - createDescriptorRunner(): DescriptorRunner; + private initializeBasicKernels(); + load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; + private initializeStaticBuffer(weightRawArray); + private initializeMetaBuffers(); + private initializeDynamicBuffer(); + private setDescriptor(descriptor); + private compile(); + setPlaceholderValue(values: { + [key: string]: number; + }): Promise; + getInputViews(): SymbolicFloat32Array[]; + getOutputViews(): SymbolicFloat32Array[]; + run(): Promise; } } declare namespace WebDNN { interface GraphDescriptorWebassembly extends GraphDescriptor { - weight_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; - variable_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; + unresolved_value_lists: { + offset: number; + placeholder: Placeholder; + }[][]; } } +declare let WebAssembly: any; declare namespace WebDNN { - class DescriptorRunnerWebassembly implements DescriptorRunner { + class DescriptorRunnerWebassembly extends DescriptorRunner { + readonly backendName: string; private inputViews; private outputViews; private worker; - descriptor: GraphDescriptorWebassembly; - ignoreCache: boolean; - backend: string; private worker_entry_js_path; private worker_promise_reject_func; private worker_initial_error; - constructor(); - load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; - setDescriptor(descriptor: GraphDescriptorWebassembly): void; - compile(): Promise; - loadWeights(weightsData: Uint8Array): Promise; - getInputViews(): Promise; - getOutputViews(): Promise; - run(): Promise; - } -} -declare var WebAssembly: any; -declare namespace WebDNN { - class GPUInterfaceWebassembly implements GPUInterface { - private option; constructor(option?: any); init(): Promise; - createDescriptorRunner(): DescriptorRunner; + load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; + setPlaceholderValue(values: { + [key: string]: number; + }): Promise; + private setPlaceholderValueWorker(dynamicBufferSize, metaBufferFillArray); + private compile(); + private loadWeights(weightsData); + getInputViews(): SymbolicFloat32Array[]; + getOutputViews(): SymbolicFloat32Array[]; + run(): Promise; } } declare namespace WebDNN { interface GraphDescriptorFallback extends GraphDescriptor { - weight_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; - variable_allocation: { - total_size: number; - allocation: { - [index: string]: { - name: string; - offset: number; - size: number; - }; - }; - }; + memory_layout: MemoryLayout; kernel_source: string; exec_infos: GraphDescriptorFallbackExecInfo[]; } @@ -448,42 +472,38 @@ declare namespace WebDNN { call_option: any; } } +declare function wait(duration?: number): Promise<{}>; declare namespace WebDNN { - class DescriptorRunnerFallback implements DescriptorRunner { - descriptor: GraphDescriptorFallback; - kernelObj: any; - rawWeightArray: Float32Array; - weightArrays: Map; - variableArrays: Map; - ignoreCache: boolean; - backend: string; + class DescriptorRunnerFallback extends DescriptorRunner { + readonly backendName: string; + private kernelObj; + private variableMap; private inputViews; private outputViews; - constructor(); + private staticBuffer; + private dynamicBuffer; + constructor(option?: any); + init(): Promise; load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; - setDescriptor(descriptor: GraphDescriptorFallback): void; - compile(): Promise; - private compileKernel(); - loadWeights(weightsData: Uint8Array): Promise; + private setDescriptor(descriptor); + private compile(); + private initializeStaticBuffer(weightRawArray); + private initializeDynamicBuffer(); + setPlaceholderValue(values: { + [key: string]: number; + }): Promise; run(): Promise; - wait_to_display(): Promise<{}>; - getInputViews(): Promise; - getOutputViews(): Promise; - } -} -declare namespace WebDNN { - class GPUInterfaceFallback implements GPUInterface { - private option; - constructor(option?: any); - init(option?: any): Promise; - createDescriptorRunner(): DescriptorRunner; + getInputViews(): SymbolicFloat32Array[]; + getOutputViews(): SymbolicFloat32Array[]; } } declare namespace WebDNN { - let gpu: GPUInterface; - function init(backendOrder?: string | string[], backendOptions?: { - [key: string]: any; - }): Promise; + const backends: { + 'webgpu': typeof DescriptorRunnerWebGPU; + 'webassembly': typeof DescriptorRunnerWebassembly; + 'fallback': typeof DescriptorRunnerFallback; + }; + let DEBUG: boolean; /** * Prepare backend interface and load model data at once. Internally calls init(). * @param backendOrder The trying order of backend names to be initialized. @@ -495,6 +515,7 @@ declare namespace WebDNN { backendOptions?: { [key: string]: any; }; + ignoreCache?: boolean; progressCallback?: (loaded: number, total: number) => any; } /** @@ -503,28 +524,7 @@ declare namespace WebDNN { * @param initOption Initialize option * @return Interface to input/output data and run the model. */ - function prepareAll(directory: string, initOption?: InitOption): Promise; - /** - * Interface to input/output data and run the model. - */ - interface GraphInterface { - /** - * The name of backend. - */ - backendName: string; - /** - * The buffers to write input data. - */ - inputViews: Float32Array[]; - /** - * The buffers to read output data. - */ - outputViews: Float32Array[]; - /** - * Run the model. - */ - run: () => Promise; - } + function load(directory: string, initOption?: InitOption): Promise>; } declare namespace WebDNN { namespace Math { diff --git a/dist/webdnn.es5.js b/dist/webdnn.es5.js index 55450dbe5..a8901143c 100644 --- a/dist/webdnn.es5.js +++ b/dist/webdnn.es5.js @@ -66,8 +66,191 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +var WebDNN; +(function (WebDNN) { + /** + * PlaceholderContext manages the placeholders + */ + var PlaceholderContext = (function () { + function PlaceholderContext(values) { + this.values = {}; + if (values) { + this.update(values); + } + } + Object.defineProperty(PlaceholderContext.prototype, "isResolved", { + get: function () { + return Object.values(this.values).every(function (value) { return typeof value == 'number'; }); + }, + enumerable: true, + configurable: true + }); + PlaceholderContext.prototype.update = function (values) { + this.values = Object.assign(this.values, values); + }; + PlaceholderContext.prototype.resolve = function (placeholder) { + var _this = this; + // Literal value => return itself. + if (typeof placeholder !== 'object') + return placeholder; + // Placeholder object ( { eval: string } ) => resolve + if (Object.keys(placeholder).length == 1 && 'eval' in placeholder) { + if (!this.isResolved) + throw Error("Not all placeholders are resolved: " + this); + return (function (placeholders) { return eval(placeholder.eval); })(this.values); + } + // Array => deep copy + if (placeholder instanceof Array) { + return placeholder.map(function (value) { return _this.resolve(value); }); + } + // Object => deep copy + return Object.entries(placeholder) + .reduce(function (result, _a) { + var key = _a[0], value = _a[1]; + result[key] = _this.resolve(value); + return result; + }, {}); + }; + PlaceholderContext.prototype.toString = function () { + return JSON.stringify(this.values); + }; + return PlaceholderContext; + }()); + WebDNN.PlaceholderContext = PlaceholderContext; +})(WebDNN || (WebDNN = {})); +/// +/// +/// +var WebDNN; +(function (WebDNN) { + var SymbolicArrayBufferView = (function () { + function SymbolicArrayBufferView(allocation, placeholderContext, ignoreOffsetOnActual) { + if (ignoreOffsetOnActual === void 0) { ignoreOffsetOnActual = false; } + this.ignoreOffsetOnActual = ignoreOffsetOnActual; + this.allocation = allocation; + if (this.isDynamic) { + if (!placeholderContext) { + throw Error('PlaceholderContext must be required when SymbolicArrayBufferView is initialized as dynamic buffer view.'); + } + } + this.placeholderContext = placeholderContext; + } + SymbolicArrayBufferView.prototype.setArrayBuffer = function (arrayBuffer) { + this.arrayBuffer = arrayBuffer; + }; + Object.defineProperty(SymbolicArrayBufferView.prototype, "isDynamic", { + get: function () { + return (typeof this.allocation.offset !== 'number' || typeof this.allocation.size !== 'number'); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SymbolicArrayBufferView.prototype, "offset", { + get: function () { + //TODO + if (this.isDynamic) { + return this.placeholderContext.resolve(this.allocation.offset); + } + else { + return this.allocation.offset; + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SymbolicArrayBufferView.prototype, "length", { + get: function () { + if (this.isDynamic) { + return this.placeholderContext.resolve(this.allocation.size); + } + else { + return this.allocation.size; + } + }, + enumerable: true, + configurable: true + }); + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + SymbolicArrayBufferView.prototype.set = function (array, offset) { + return this.toActual().set(array, offset); + }; + return SymbolicArrayBufferView; + }()); + WebDNN.SymbolicArrayBufferView = SymbolicArrayBufferView; + var SymbolicFloat32Array = (function (_super) { + __extends(SymbolicFloat32Array, _super); + function SymbolicFloat32Array() { + return _super !== null && _super.apply(this, arguments) || this; + } + SymbolicFloat32Array.prototype.toActual = function () { + if (!this.arrayBuffer) { + throw new Error('Internal buffer for this variable is not set. DescriptorRunner.setPlaceholderValue() have to be called before calling this function.'); + } + return new Float32Array(this.arrayBuffer, this.ignoreOffsetOnActual ? 0 : this.offset * Float32Array.BYTES_PER_ELEMENT, this.length); + }; + return SymbolicFloat32Array; + }(SymbolicArrayBufferView)); + WebDNN.SymbolicFloat32Array = SymbolicFloat32Array; + var SymbolicInt32Array = (function (_super) { + __extends(SymbolicInt32Array, _super); + function SymbolicInt32Array() { + return _super !== null && _super.apply(this, arguments) || this; + } + SymbolicInt32Array.prototype.toActual = function () { + if (!this.arrayBuffer) { + throw new Error('Internal buffer for this variable is not set. DescriptorRunner.setPlaceholderValue() have to be called before calling this function.'); + } + return new Int32Array(this.arrayBuffer, this.ignoreOffsetOnActual ? 0 : this.offset * Int32Array.BYTES_PER_ELEMENT, this.length); + }; + return SymbolicInt32Array; + }(SymbolicArrayBufferView)); + WebDNN.SymbolicInt32Array = SymbolicInt32Array; +})(WebDNN || (WebDNN = {})); /// -/// +/// +/// +var WebDNN; +(function (WebDNN) { + /** + * `DescriptorRunner` executes computation based on `GraphDescriptor`. + * + * Typically, DescriptorRunner takes 3 steps to execute DNN model. + * + * 1. Initialize static configurations + * + * Initialize things independent from runtime configuration. + * + * - `init()` + * - `load()` + * + * 2. Initialize dynamic configurations + * + * Initialize things depend on runtime configuration such as batch size, input image size, etc. + * + * - `setPlaceholderValue()` + * - `getInputViews()` + * - `getOutputViews()` + * + * 3. Execute the model + * + * - `run()` + * + * You need to do step 1 and 2 only once. We recommend to call `WebDNN.prepareAll()` instead + * to call `GraphDescriptor#load()` directly. In that method, all procedures in step 1 and 2 are performed. + */ + var DescriptorRunner = (function () { + function DescriptorRunner() { + this.descriptor = null; + this.ignoreCache = false; + } + return DescriptorRunner; + }()); + WebDNN.DescriptorRunner = DescriptorRunner; +})(WebDNN || (WebDNN = {})); var WebDNN; (function (WebDNN) { /** @@ -185,13 +368,15 @@ var WebDNN; } WebGPUHandler.prototype.init = function () { return __awaiter(this, void 0, void 0, function () { + var context; return __generator(this, function (_a) { - // asynchronous operation may be added in future - if (!WebGPUHandler.isBrowserSupported) { + if (!WebGPUHandler.isBrowserSupported) throw new Error('This browser does not support WebGPU'); - } - this.context = document.createElement('canvas').getContext('webgpu'); //force cast - this.commandQueue = this.context.createCommandQueue(); + context = document.createElement('canvas').getContext('webgpu'); + if (!context) + throw new Error('WebGPURenderingContext initialization failed'); + this.context = context; + this.commandQueue = context.createCommandQueue(); this.pipelineStates = new Map(); return [2 /*return*/]; }); @@ -273,13 +458,14 @@ var WebDNN; WebDNN.WebGPUHandler = WebGPUHandler; WebGPUHandler.isBrowserSupported = 'WebGPURenderingContext' in window && 'WebGPUComputeCommandEncoder' in window; })(WebDNN || (WebDNN = {})); +/// /// var WebDNN; (function (WebDNN) { var WeightDecoderRaw = (function () { function WeightDecoderRaw() { } - WeightDecoderRaw.prototype.decode = function (data, weight_allocation) { + WeightDecoderRaw.prototype.decode = function (data, memory_layout) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, new Float32Array(data.buffer, data.byteOffset, data.byteLength / 4)]; @@ -290,18 +476,17 @@ var WebDNN; }()); WebDNN.WeightDecoderRaw = WeightDecoderRaw; })(WebDNN || (WebDNN = {})); -/// /// var WebDNN; (function (WebDNN) { var WeightDecoderEightbit = (function () { function WeightDecoderEightbit() { } - WeightDecoderEightbit.prototype.decode = function (data, weight_allocation) { + WeightDecoderEightbit.prototype.decode = function (data, memory_layout) { return __awaiter(this, void 0, void 0, function () { var dst, data_view, src_offset, dst_offset, body_size, scale, scaled_table, i, src_data_view, inflate, decompressed, dec_size, s; return __generator(this, function (_a) { - dst = new Float32Array(weight_allocation.total_size); + dst = new Float32Array(memory_layout.static.size); data_view = new DataView(data.buffer, data.byteOffset); src_offset = 0; while (src_offset < data.length) { @@ -457,10 +642,32 @@ var WebDNN; * Fetch function. WebDNN API use this fetch function instead of original fetch function. * @param input Requested url * @param init Additional information about fetch + * @param init.ignoreCache If true, cache is ignored by appending '?t=(timestamp)' to the end of request url. * @returns Response */ function fetch(input, init) { - return fetchDelegate(input, init); + return __awaiter(this, void 0, void 0, function () { + var res; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (typeof input == 'string') { + input = transformUrl(input) + ((init && init.ignoreCache) ? '?t=' + Date.now() : ''); + } + else { + input = Object.assign({}, input, { + url: transformUrl(input.url) + ((init && init.ignoreCache) ? '?t=' + Date.now() : '') + }); + } + return [4 /*yield*/, fetchDelegate(input, init)]; + case 1: + res = _a.sent(); + if (!res.ok) + throw new Error("Fetch returns status code " + res.status + ": " + res.statusText); + return [2 /*return*/, res]; + } + }); + }); } WebDNN.fetch = fetch; /** @@ -505,158 +712,307 @@ var WebDNN; /// /// /// +/// +/// +var IS_IOS = navigator.userAgent.includes('iPhone'); var WebDNN; (function (WebDNN) { - var DescriptorRunnerWebGPU = (function () { - function DescriptorRunnerWebGPU(webGPUHandler) { - this.webGPUHandler = webGPUHandler; - this.ignoreCache = false; - this.backend = 'webgpu'; + var DescriptorRunnerWebGPU = (function (_super) { + __extends(DescriptorRunnerWebGPU, _super); + //noinspection JSUnusedLocalSymbols + function DescriptorRunnerWebGPU(option) { + var _this = _super.call(this) || this; + _this.backendName = 'webgpu'; + if (!WebDNN.WebGPUHandler.isBrowserSupported) { + throw new Error('WebGPU is not supported on this browser'); + } + return _this; } - DescriptorRunnerWebGPU.prototype.load = function (directory, progressCallback) { + DescriptorRunnerWebGPU.prototype.init = function () { return __awaiter(this, void 0, void 0, function () { - var graph_url, graph_fetch, _a, weight_url, weights_data_ab, _b; - return __generator(this, function (_c) { - switch (_c.label) { + return __generator(this, function (_a) { + switch (_a.label) { case 0: - graph_url = directory + "/graph_" + this.backend + ".json"; - if (this.ignoreCache) { - graph_url += '?t=' + Date.now(); - } - graph_url = WebDNN.transformUrl(graph_url); - return [4 /*yield*/, WebDNN.fetch(graph_url)]; + // initialize webgpu, build kernels + this.shaderLanguage = 'metal'; + this.webgpuHandler = new WebDNN.WebGPUHandler(); + return [4 /*yield*/, this.webgpuHandler.init()]; case 1: - graph_fetch = _c.sent(); - if (!graph_fetch.ok) { - throw new Error(graph_url + " cannot be loaded"); - } - _a = this; - return [4 /*yield*/, graph_fetch.json()]; - case 2: - _a.descriptor = _c.sent(); - return [4 /*yield*/, this.compile()]; - case 3: - _c.sent(); - weight_url = directory + "/weight_" + this.backend + ".bin"; - if (this.ignoreCache) { - weight_url += '?t=' + Date.now(); - } - weight_url = WebDNN.transformUrl(weight_url); - _b = WebDNN.readArrayBufferProgressively; - return [4 /*yield*/, WebDNN.fetch(weight_url, progressCallback)]; - case 4: return [4 /*yield*/, _b.apply(void 0, [_c.sent(), progressCallback])]; - case 5: - weights_data_ab = _c.sent(); - return [4 /*yield*/, this.loadWeights(new Uint8Array(weights_data_ab))]; - case 6: - _c.sent(); + _a.sent(); + WebDNN.BufferWebGPU.init(this.webgpuHandler); + this.initializeBasicKernels(); return [2 /*return*/]; } }); }); }; - DescriptorRunnerWebGPU.prototype.setDescriptor = function (descriptor) { - this.descriptor = descriptor; + DescriptorRunnerWebGPU.prototype.initializeBasicKernels = function () { + this.webgpuHandler.loadKernel('kernel void sync(){}', 'basic'); }; - DescriptorRunnerWebGPU.prototype.compile = function () { + DescriptorRunnerWebGPU.prototype.load = function (directory, progressCallback) { return __awaiter(this, void 0, void 0, function () { - var i, exec_info, buf; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - this.webGPUHandler.loadKernel(this.descriptor.kernel_source, 'descriptor'); - this.weightMat = new WebDNN.BufferWebGPU(this.descriptor.weight_allocation.total_size * Float32Array.BYTES_PER_ELEMENT); - this.dataMat = new WebDNN.BufferWebGPU(this.descriptor.variable_allocation.total_size * Float32Array.BYTES_PER_ELEMENT); - this.metaBufferGPUBuffers = []; - i = 0; - _a.label = 1; + var _a, descriptor, weightRawArray; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, Promise.all([ + WebDNN.fetch(directory + "/graph_" + this.backendName + ".json", { ignoreCache: this.ignoreCache }) + .then(function (res) { return res.json(); }), + WebDNN.fetch(directory + "/weight_" + this.backendName + ".bin", { ignoreCache: this.ignoreCache }) + .then(function (res) { return WebDNN.readArrayBufferProgressively(res, progressCallback); }) + ])]; case 1: - if (!(i < this.descriptor.exec_infos.length)) return [3 /*break*/, 4]; - exec_info = this.descriptor.exec_infos[i]; - buf = new WebDNN.BufferWebGPU(exec_info.meta_buffer.length * Float32Array.BYTES_PER_ELEMENT); - return [4 /*yield*/, buf.write(new Uint8Array(exec_info.meta_buffer))]; + _a = _b.sent(), descriptor = _a[0], weightRawArray = _a[1]; + return [4 /*yield*/, this.setDescriptor(descriptor)]; case 2: - _a.sent(); - this.metaBufferGPUBuffers.push(buf); - _a.label = 3; + _b.sent(); + return [4 /*yield*/, this.compile()]; case 3: - i++; - return [3 /*break*/, 1]; - case 4: return [2 /*return*/]; + _b.sent(); + return [4 /*yield*/, this.initializeStaticBuffer(weightRawArray)]; + case 4: + _b.sent(); + return [4 /*yield*/, this.initializeMetaBuffers()]; + case 5: + _b.sent(); + return [4 /*yield*/, this.setPlaceholderValue({ + '__MAX_THREADS_PER_THREADGROUP__': IS_IOS ? 512 : 512 + })]; + case 6: + _b.sent(); + if (!(this.placeholderContext && this.placeholderContext.isResolved)) return [3 /*break*/, 8]; + return [4 /*yield*/, this.initializeDynamicBuffer()]; + case 7: + _b.sent(); + _b.label = 8; + case 8: return [2 /*return*/]; } }); }); }; - DescriptorRunnerWebGPU.prototype.loadWeights = function (weightsData) { + DescriptorRunnerWebGPU.prototype.initializeStaticBuffer = function (weightRawArray) { return __awaiter(this, void 0, void 0, function () { - var decoder, _a, _b; + var descriptor, staticBuffer, decoder, _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: - decoder = WebDNN.get_weight_decoder(this.descriptor.weight_encoding); - _b = (_a = this.weightMat).write; - return [4 /*yield*/, decoder.decode(weightsData, this.descriptor.weight_allocation)]; + if (!this.descriptor) + throw Error("GraphDescriptor is not loaded."); + descriptor = this.descriptor; + staticBuffer = new WebDNN.BufferWebGPU(descriptor.memory_layout.static.size * Float32Array.BYTES_PER_ELEMENT); + this.staticBuffer = staticBuffer; + decoder = WebDNN.get_weight_decoder(descriptor.weight_encoding); + _b = (_a = staticBuffer).write; + return [4 /*yield*/, decoder.decode(new Uint8Array(weightRawArray), descriptor.memory_layout)]; case 1: return [4 /*yield*/, _b.apply(_a, [_c.sent()])]; case 2: _c.sent(); + return [4 /*yield*/, this.getInputViews()]; + case 3: + (_c.sent()) + .filter(function (view) { return !view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer(staticBuffer.bufferView.buffer); }); + return [4 /*yield*/, this.getOutputViews()]; + case 4: + (_c.sent()) + .filter(function (view) { return !view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer(staticBuffer.bufferView.buffer); }); return [2 /*return*/]; } }); }); }; - DescriptorRunnerWebGPU.prototype.getInputViews = function () { + DescriptorRunnerWebGPU.prototype.initializeMetaBuffers = function () { return __awaiter(this, void 0, void 0, function () { - var views, i, var_alloc; - return __generator(this, function (_a) { - if (this.inputViews) { - return [2 /*return*/, this.inputViews]; - } - views = []; - for (i = 0; i < this.descriptor.inputs.length; i++) { - var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.inputs[i]]; - views.push(this.dataMat.getWriteView(var_alloc.offset, var_alloc.size, Float32Array)); + var _this = this; + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!this.descriptor) + throw Error("GraphDescriptor is not loaded."); + _a = this; + return [4 /*yield*/, Promise.all(this.descriptor.exec_infos.map(function (executionInfo) { return __awaiter(_this, void 0, void 0, function () { + var buffer; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + buffer = new WebDNN.BufferWebGPU(executionInfo.meta_buffer.length * Int32Array.BYTES_PER_ELEMENT); + return [4 /*yield*/, buffer.write(new Uint8Array(executionInfo.meta_buffer))]; + case 1: + _a.sent(); + return [2 /*return*/, buffer]; + } + }); + }); }))]; + case 1: + _a.metaBuffers = _b.sent(); + return [2 /*return*/]; } - this.inputViews = views; - return [2 /*return*/, views]; }); }); }; - DescriptorRunnerWebGPU.prototype.getOutputViews = function () { + DescriptorRunnerWebGPU.prototype.initializeDynamicBuffer = function () { return __awaiter(this, void 0, void 0, function () { - var views, i, var_alloc; + var descriptor, placeholderContext, dynamicBufferSize, dynamicBuffer; return __generator(this, function (_a) { - if (this.outputViews) { - return [2 /*return*/, this.outputViews]; + switch (_a.label) { + case 0: + if (!this.descriptor) + throw Error("GraphDescriptor is not loaded."); + if (!this.placeholderContext) + throw Error("PlaceholderContext is not initialized."); + descriptor = this.descriptor; + placeholderContext = this.placeholderContext; + dynamicBufferSize = placeholderContext.resolve(descriptor.memory_layout.dynamic.size); + dynamicBuffer = new WebDNN.BufferWebGPU(dynamicBufferSize * Float32Array.BYTES_PER_ELEMENT); + this.dynamicBuffer = dynamicBuffer; + return [4 /*yield*/, this.getInputViews()]; + case 1: + (_a.sent()) + .filter(function (view) { return view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer(dynamicBuffer.bufferView.buffer); }); + return [4 /*yield*/, this.getOutputViews()]; + case 2: + (_a.sent()) + .filter(function (view) { return view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer(dynamicBuffer.bufferView.buffer); }); + return [2 /*return*/]; } - views = []; - for (i = 0; i < this.descriptor.outputs.length; i++) { - var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.outputs[i]]; - views.push(this.dataMat.getReadView(var_alloc.offset, var_alloc.size, Float32Array)); + }); + }); + }; + DescriptorRunnerWebGPU.prototype.setDescriptor = function (descriptor) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + this.descriptor = descriptor; + //reset all datum depend on old descriptor + this.staticBuffer = null; + this.dynamicBuffer = null; + this.metaBuffers = null; + this.placeholderContext = new WebDNN.PlaceholderContext(descriptor.placeholders); + this.executionInfos = descriptor.exec_infos; + return [2 /*return*/]; + }); + }); + }; + DescriptorRunnerWebGPU.prototype.compile = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + this.webgpuHandler.loadKernel(this.descriptor.kernel_source, 'descriptor'); + return [2 /*return*/]; + }); + }); + }; + DescriptorRunnerWebGPU.prototype.setPlaceholderValue = function (values) { + return __awaiter(this, void 0, void 0, function () { + var _this = this; + var placeholderContext, descriptor, metaBuffers, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized.'); + placeholderContext = this.placeholderContext; + placeholderContext.update(values); + if (!placeholderContext.isResolved) + return [2 /*return*/]; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.metaBuffers) + throw new Error('MetaBuffers are not initialized'); + descriptor = this.descriptor; + metaBuffers = this.metaBuffers; + return [4 /*yield*/, this.initializeDynamicBuffer()]; + case 1: + _b.sent(); + // resolve placeholders in execution info + _a = this; + return [4 /*yield*/, Promise.all(descriptor.exec_infos.map(function (executionInfo, i) { return __awaiter(_this, void 0, void 0, function () { + var bufferView, _i, _a, unresolved_value; + return __generator(this, function (_b) { + bufferView = new Int32Array(metaBuffers[i].bufferView.buffer); + for (_i = 0, _a = executionInfo.unresolved_value_list; _i < _a.length; _i++) { + unresolved_value = _a[_i]; + bufferView[unresolved_value.offset] = placeholderContext.resolve(unresolved_value.placeholder); + } + return [2 /*return*/, placeholderContext.resolve(executionInfo)]; + }); + }); }))]; + case 2: + // resolve placeholders in execution info + _a.executionInfos = _b.sent(); + return [2 /*return*/]; } - this.outputViews = views; - return [2 /*return*/, views]; }); }); }; + DescriptorRunnerWebGPU.prototype.getInputViews = function () { + if (this.inputViews) + return this.inputViews; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + var descriptor = this.descriptor; + var placeholderContext = this.placeholderContext; + this.inputViews = descriptor.inputs.map(function (name) { + var allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + var view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext); + return view; + }); + return this.inputViews; + }; + DescriptorRunnerWebGPU.prototype.getOutputViews = function () { + if (this.outputViews) + return this.outputViews; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + var descriptor = this.descriptor; + var placeholderContext = this.placeholderContext; + this.outputViews = descriptor.outputs.map(function (name) { + var allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + var view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext); + return view; + }); + return this.outputViews; + }; DescriptorRunnerWebGPU.prototype.run = function () { return __awaiter(this, void 0, void 0, function () { - var records, totalElapsedTime_1, i, exec_info, start, elapsedTime, summary, complete_promise, i, exec_info, is_last; + var staticBuffer, dynamicBuffer, metaBuffers, records, totalElapsedTime_1, i, exec_info, start, elapsedTime, summary, complete_promise, i, exec_info, is_last; return __generator(this, function (_a) { switch (_a.label) { case 0: - if (!this.inputViews || !this.outputViews) { + if (!this.executionInfos) + throw new Error('ExecutionInfos is not loaded'); + if (!this.inputViews || !this.outputViews) throw new Error('getInputViews and getOutputViews must be called prior to run'); - } - if (!window['PROFILE']) return [3 /*break*/, 5]; + if (!this.staticBuffer) + throw new Error('StaticBuffer is not initialized'); + if (!this.dynamicBuffer) + throw new Error('DynamicBuffer is not initialized'); + if (!this.metaBuffers) + throw new Error('MetaBuffer is not initialized'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + if (!this.placeholderContext.isResolved) + throw new Error("Not all placeholders are resolved: " + this.placeholderContext); + staticBuffer = this.staticBuffer; + dynamicBuffer = this.dynamicBuffer; + metaBuffers = this.metaBuffers; + if (!WebDNN.DEBUG) return [3 /*break*/, 5]; records = []; totalElapsedTime_1 = 0; i = 0; _a.label = 1; case 1: - if (!(i < this.descriptor.exec_infos.length)) return [3 /*break*/, 4]; - exec_info = this.descriptor.exec_infos[i]; + if (!(i < this.executionInfos.length)) return [3 /*break*/, 4]; + exec_info = this.executionInfos[i]; start = performance.now(); - return [4 /*yield*/, this.webGPUHandler.executeSinglePipelineState('descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, [this.weightMat, this.dataMat, this.metaBufferGPUBuffers[i]], true)]; + return [4 /*yield*/, this.webgpuHandler.executeSinglePipelineState('descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, [staticBuffer, dynamicBuffer, metaBuffers[i]], true)]; case 2: _a.sent(); elapsedTime = performance.now() - start; @@ -688,10 +1044,10 @@ var WebDNN; return [3 /*break*/, 7]; case 5: complete_promise = null; - for (i = 0; i < this.descriptor.exec_infos.length; i++) { - exec_info = this.descriptor.exec_infos[i]; - is_last = i == this.descriptor.exec_infos.length - 1; - complete_promise = this.webGPUHandler.executeSinglePipelineState('descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, [this.weightMat, this.dataMat, this.metaBufferGPUBuffers[i]], is_last); + for (i = 0; i < this.executionInfos.length; i++) { + exec_info = this.executionInfos[i]; + is_last = i == this.executionInfos.length - 1; + complete_promise = this.webgpuHandler.executeSinglePipelineState('descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, [staticBuffer, dynamicBuffer, metaBuffers[i]], is_last); } return [4 /*yield*/, complete_promise]; case 6: @@ -703,47 +1059,9 @@ var WebDNN; }); }; return DescriptorRunnerWebGPU; - }()); + }(WebDNN.DescriptorRunner)); WebDNN.DescriptorRunnerWebGPU = DescriptorRunnerWebGPU; })(WebDNN || (WebDNN = {})); -/// -var WebDNN; -(function (WebDNN) { - var GPUInterfaceWebGPU = (function () { - function GPUInterfaceWebGPU(option) { - this.option = option; - if (!WebDNN.WebGPUHandler.isBrowserSupported) { - throw new Error('WebGPU is not supported on this browser'); - } - } - GPUInterfaceWebGPU.prototype.init = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - // initialize webgpu, build kernels - this.shaderLanguage = 'metal'; - this.webgpuHandler = new WebDNN.WebGPUHandler(); - return [4 /*yield*/, this.webgpuHandler.init()]; - case 1: - _a.sent(); - WebDNN.BufferWebGPU.init(this.webgpuHandler); - this.init_basic_kernels(); - return [2 /*return*/]; - } - }); - }); - }; - GPUInterfaceWebGPU.prototype.init_basic_kernels = function () { - this.webgpuHandler.loadKernel('kernel void sync(){}', 'basic'); - }; - GPUInterfaceWebGPU.prototype.createDescriptorRunner = function () { - return new WebDNN.DescriptorRunnerWebGPU(this.webgpuHandler); - }; - return GPUInterfaceWebGPU; - }()); - WebDNN.GPUInterfaceWebGPU = GPUInterfaceWebGPU; -})(WebDNN || (WebDNN = {})); /// /// /// @@ -752,34 +1070,40 @@ var WebDNN; /// var WebDNN; (function (WebDNN) { - var DescriptorRunnerWebassembly = (function () { - function DescriptorRunnerWebassembly() { - this.ignoreCache = false; - this.backend = 'webassembly'; - this.worker_promise_reject_func = null; - this.worker_initial_error = null; + var DescriptorRunnerWebassembly = (function (_super) { + __extends(DescriptorRunnerWebassembly, _super); + function DescriptorRunnerWebassembly(option) { + var _this = _super.call(this) || this; + _this.backendName = 'webassembly'; + _this.worker_promise_reject_func = null; + _this.worker_initial_error = null; + if (typeof Worker === 'undefined') { + throw new Error('WebWorker is needed for WebAssembly backend'); + } + if (typeof WebAssembly !== 'object') { + console.warn('WebAssembly is not supported on this browser, trying to use asm.js code'); + } + return _this; } + DescriptorRunnerWebassembly.prototype.init = function () { + //nothing to do + return Promise.resolve(); + }; DescriptorRunnerWebassembly.prototype.load = function (directory, progressCallback) { return __awaiter(this, void 0, void 0, function () { - var graph_url, graph_fetch, _a, kernel_backend, worker_entry_js_path, weight_url, weights_data_ab, _b; - return __generator(this, function (_c) { - switch (_c.label) { + var graph_url, graph_fetch, _a, kernel_backend, worker_entry_js_path, weight_url, weight_fetch, weights_data_ab; + return __generator(this, function (_b) { + switch (_b.label) { case 0: - graph_url = directory + "/graph_" + this.backend + ".json"; - if (this.ignoreCache) { - graph_url += '?t=' + Date.now(); - } - graph_url = WebDNN.transformUrl(graph_url); - return [4 /*yield*/, WebDNN.fetch(graph_url)]; + graph_url = directory + "/graph_" + this.backendName + ".json"; + return [4 /*yield*/, WebDNN.fetch(graph_url, { ignoreCache: this.ignoreCache })]; case 1: - graph_fetch = _c.sent(); - if (!graph_fetch.ok) { - throw new Error(graph_url + " cannot be loaded"); - } + graph_fetch = _b.sent(); _a = this; return [4 /*yield*/, graph_fetch.json()]; case 2: - _a.descriptor = _c.sent(); + _a.descriptor = _b.sent(); + this.placeholderContext = new WebDNN.PlaceholderContext(this.descriptor.placeholders); kernel_backend = typeof WebAssembly === 'object' ? 'webassembly' : 'asmjs'; worker_entry_js_path = directory + "/kernels_" + kernel_backend + ".js"; if (this.ignoreCache) { @@ -789,33 +1113,103 @@ var WebDNN; this.worker_entry_js_path = worker_entry_js_path; return [4 /*yield*/, this.compile()]; case 3: - _c.sent(); - weight_url = directory + "/weight_" + this.backend + ".bin"; - if (this.ignoreCache) { - weight_url += '?t=' + Date.now(); - } - weight_url = WebDNN.transformUrl(weight_url); - _b = WebDNN.readArrayBufferProgressively; - return [4 /*yield*/, WebDNN.fetch(weight_url)]; - case 4: return [4 /*yield*/, _b.apply(void 0, [_c.sent(), progressCallback])]; + _b.sent(); + weight_url = directory + "/weight_" + this.backendName + ".bin"; + return [4 /*yield*/, WebDNN.fetch(weight_url, { ignoreCache: this.ignoreCache })]; + case 4: + weight_fetch = _b.sent(); + return [4 /*yield*/, WebDNN.readArrayBufferProgressively(weight_fetch, progressCallback)]; case 5: - weights_data_ab = _c.sent(); + weights_data_ab = _b.sent(); return [4 /*yield*/, this.loadWeights(new Uint8Array(weights_data_ab))]; case 6: - _c.sent(); + _b.sent(); + return [4 /*yield*/, this.getInputViews()]; + case 7: + //assign buffer to input/output buffer view + (_b.sent()) + .filter(function (view) { return !view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer((new Float32Array(view.length)).buffer); }); + return [4 /*yield*/, this.getOutputViews()]; + case 8: + (_b.sent()) + .filter(function (view) { return !view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer((new Float32Array(view.length)).buffer); }); return [2 /*return*/]; } }); }); }; - DescriptorRunnerWebassembly.prototype.setDescriptor = function (descriptor) { - this.descriptor = descriptor; + DescriptorRunnerWebassembly.prototype.setPlaceholderValue = function (values) { + return __awaiter(this, void 0, void 0, function () { + var placeholderContext, descriptor, unresolvedValueLists, metaBufferFillList, _loop_1, kernel_order, dynamicBufferSize; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized.'); + placeholderContext = this.placeholderContext; + placeholderContext.update(values); + if (!placeholderContext.isResolved) + return [2 /*return*/]; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + descriptor = this.descriptor; + unresolvedValueLists = descriptor.unresolved_value_lists; + metaBufferFillList = []; + _loop_1 = function (kernel_order) { + var unresolvedValueList = unresolvedValueLists[kernel_order]; + unresolvedValueList.forEach(function (offset_placeholder) { + var resolved_value = placeholderContext.resolve(offset_placeholder.placeholder); + metaBufferFillList.push(kernel_order, offset_placeholder.offset, resolved_value); + }); + }; + for (kernel_order = 0; kernel_order < unresolvedValueLists.length; kernel_order++) { + _loop_1(kernel_order); + } + return [4 /*yield*/, this.getInputViews()]; + case 1: + (_a.sent()) + .filter(function (view) { return view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer((new Float32Array(view.length)).buffer); }); + return [4 /*yield*/, this.getOutputViews()]; + case 2: + (_a.sent()) + .filter(function (view) { return view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer((new Float32Array(view.length)).buffer); }); + dynamicBufferSize = this.placeholderContext.resolve(this.descriptor.memory_layout.dynamic.size); + return [4 /*yield*/, this.setPlaceholderValueWorker(dynamicBufferSize, new Int32Array(metaBufferFillList))]; + case 3: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + DescriptorRunnerWebassembly.prototype.setPlaceholderValueWorker = function (dynamicBufferSize, metaBufferFillArray) { + if (!this.worker) + throw Error("Worker is not initialized"); + var worker = this.worker; + return new Promise(function (resolve, reject) { + worker.onmessage = function (event) { + if (event.data === 0) { + resolve(); + } + else { + console.log(event.data); + worker.terminate(); + reject(new Error(event.data)); + } + }; + worker.postMessage({ type: 'set_dynamic_buffer', size: dynamicBufferSize, data: metaBufferFillArray }); + }); }; DescriptorRunnerWebassembly.prototype.compile = function () { var _this = this; - this.worker = new Worker(this.worker_entry_js_path); - this.worker.onerror = function (event) { - console.error('Worker Exception: ' + event.message); + var worker = new Worker(this.worker_entry_js_path); + worker.onerror = function (event) { + console.error(event); + // console.error('Worker Exception: ' + event.message); if (_this.worker_promise_reject_func) { _this.worker_promise_reject_func(event); } @@ -824,48 +1218,53 @@ var WebDNN; } }; var promise = new Promise(function (resolve, reject) { - if (_this.worker_initial_error) { - // occurs when this.worker_entry_js_path is 404 - reject(_this.worker_initial_error); - return; - } + // occurs when this.worker_entry_js_path is 404 + if (_this.worker_initial_error) + return reject(_this.worker_initial_error); _this.worker_promise_reject_func = reject; - _this.worker.onmessage = function (event) { + worker.onmessage = function (event) { if (event.data === 0) { resolve(); } else { - _this.worker.terminate(); + console.error(event.data); + worker.terminate(); reject(new Error(event.data)); } }; - //this.worker.postMessage({ type: 'init' }); }); + this.worker = worker; return promise; }; DescriptorRunnerWebassembly.prototype.loadWeights = function (weightsData) { return __awaiter(this, void 0, void 0, function () { var _this = this; - var decoder, weight_data, promise; + var decoder, weight_data, worker, promise; return __generator(this, function (_a) { switch (_a.label) { case 0: + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.worker) + throw new Error('Worker is not initialized'); decoder = WebDNN.get_weight_decoder(this.descriptor.weight_encoding); - return [4 /*yield*/, decoder.decode(weightsData, this.descriptor.weight_allocation)]; + return [4 /*yield*/, decoder.decode(weightsData, this.descriptor.memory_layout)]; case 1: weight_data = _a.sent(); + worker = this.worker; promise = new Promise(function (resolve, reject) { _this.worker_promise_reject_func = reject; - _this.worker.onmessage = function (event) { + worker.onmessage = function (event) { if (event.data === 0) { resolve(); } else { - _this.worker.terminate(); + console.log(event.data); + worker.terminate(); reject(new Error(event.data)); } }; - _this.worker.postMessage({ type: 'weight', data: weight_data }); + worker.postMessage({ type: 'weight', data: weight_data }); }); return [2 /*return*/, promise]; } @@ -873,203 +1272,275 @@ var WebDNN; }); }; DescriptorRunnerWebassembly.prototype.getInputViews = function () { - return __awaiter(this, void 0, void 0, function () { - var views, i, var_alloc; - return __generator(this, function (_a) { - if (this.inputViews) { - return [2 /*return*/, this.inputViews]; - } - views = []; - for (i = 0; i < this.descriptor.inputs.length; i++) { - var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.inputs[i]]; - views.push(new Float32Array(var_alloc.size)); - } - this.inputViews = views; - return [2 /*return*/, views]; - }); + if (this.inputViews) + return this.inputViews; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + var descriptor = this.descriptor; + var placeholderContext = this.placeholderContext; + this.inputViews = descriptor.inputs.map(function (name) { + var allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + var view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext, true); + return view; }); + return this.inputViews; }; DescriptorRunnerWebassembly.prototype.getOutputViews = function () { - return __awaiter(this, void 0, void 0, function () { - var views, i, var_alloc; - return __generator(this, function (_a) { - if (this.outputViews) { - return [2 /*return*/, this.outputViews]; - } - views = []; - for (i = 0; i < this.descriptor.outputs.length; i++) { - var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.outputs[i]]; - views.push(new Float32Array(var_alloc.size)); - } - this.outputViews = views; - return [2 /*return*/, views]; - }); + if (this.outputViews) + return this.outputViews; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + var descriptor = this.descriptor; + var placeholderContext = this.placeholderContext; + this.outputViews = descriptor.outputs.map(function (name) { + var allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + // buffer for SymbolicFloat32Array is dedicated for IO, since computation is performed on separate memory space. + var view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext, true); + return view; }); + return this.outputViews; }; DescriptorRunnerWebassembly.prototype.run = function () { return __awaiter(this, void 0, void 0, function () { var _this = this; - var promise; + var descriptor, worker, inputViews, outputViews, promise; return __generator(this, function (_a) { - if (!this.inputViews || !this.outputViews) { + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.inputViews || !this.outputViews) throw new Error('getInputViews and getOutputViews must be called prior to run'); - } + if (!this.worker) + throw new Error('Worker is not initialized'); + descriptor = this.descriptor; + worker = this.worker; + inputViews = this.inputViews; + outputViews = this.outputViews; promise = new Promise(function (resolve, reject) { // TODO: better way not to generate function on every run _this.worker_promise_reject_func = reject; - _this.worker.onmessage = function (event) { + worker.onmessage = function (event) { if (Array.isArray(event.data)) { for (var i = 0; i < event.data.length; i++) { - _this.outputViews[i].set(event.data[i]); + outputViews[i].set(event.data[i]); } resolve(); } else { - _this.worker.terminate(); + console.log(event.data); + worker.terminate(); reject(new Error(event.data)); } }; + var allocations = [descriptor.memory_layout.static.allocations, descriptor.memory_layout.dynamic.allocations]; var inputs = []; - for (var i = 0; i < _this.descriptor.inputs.length; i++) { - var var_alloc = _this.descriptor.variable_allocation.allocation[_this.descriptor.inputs[i]]; - inputs.push({ offset: var_alloc.offset, size: var_alloc.size, data: _this.inputViews[i] }); + for (var i = 0; i < descriptor.inputs.length; i++) { + for (var allocation_space = 0; allocation_space < 2; allocation_space++) { + var var_alloc = allocations[allocation_space][descriptor.inputs[i]]; + if (var_alloc) { + var symAb = inputViews[i]; + inputs.push({ + space: allocation_space, + offset: symAb.offset, + size: symAb.length, + data: symAb.toActual() + }); + break; + } + } } var outputs = []; - for (var i = 0; i < _this.descriptor.outputs.length; i++) { - var var_alloc = _this.descriptor.variable_allocation.allocation[_this.descriptor.outputs[i]]; - outputs.push({ offset: var_alloc.offset, size: var_alloc.size }); + for (var i = 0; i < descriptor.outputs.length; i++) { + for (var allocation_space = 0; allocation_space < 2; allocation_space++) { + var var_alloc = allocations[allocation_space][descriptor.outputs[i]]; + if (var_alloc) { + var symAb = outputViews[i]; + outputs.push({ space: allocation_space, offset: symAb.offset, size: symAb.length }); + break; + } + } } - _this.worker.postMessage({ type: 'run', inputs: inputs, outputs: outputs }); + worker.postMessage({ type: 'run', inputs: inputs, outputs: outputs }); }); return [2 /*return*/, promise]; }); }); }; return DescriptorRunnerWebassembly; - }()); + }(WebDNN.DescriptorRunner)); WebDNN.DescriptorRunnerWebassembly = DescriptorRunnerWebassembly; })(WebDNN || (WebDNN = {})); -/// -/// +/// +/// +/// +function wait(duration) { + if (duration === void 0) { duration = 10; } + // let console.log to be displayed, and prevent freeze + return new Promise(function (resolve) { return setTimeout(resolve, duration); }); +} var WebDNN; (function (WebDNN) { - var GPUInterfaceWebassembly = (function () { - function GPUInterfaceWebassembly(option) { - this.option = option; - if (typeof Worker === 'undefined') { - throw new Error('WebWorker is needed for WebAssembly backend'); - } - if (typeof WebAssembly !== 'object') { - console.warn('WebAssembly is not supported on this browser, trying to use asm.js code'); - } + var DescriptorRunnerFallback = (function (_super) { + __extends(DescriptorRunnerFallback, _super); + function DescriptorRunnerFallback(option) { + var _this = _super.call(this) || this; + _this.backendName = 'fallback'; + return _this; } - GPUInterfaceWebassembly.prototype.init = function () { + DescriptorRunnerFallback.prototype.init = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/]; }); }); }; - GPUInterfaceWebassembly.prototype.createDescriptorRunner = function () { - return new WebDNN.DescriptorRunnerWebassembly(); - }; - return GPUInterfaceWebassembly; - }()); - WebDNN.GPUInterfaceWebassembly = GPUInterfaceWebassembly; -})(WebDNN || (WebDNN = {})); -/// -/// -/// -var WebDNN; -(function (WebDNN) { - var DescriptorRunnerFallback = (function () { - function DescriptorRunnerFallback() { - this.ignoreCache = false; - this.backend = 'fallback'; - } DescriptorRunnerFallback.prototype.load = function (directory, progressCallback) { return __awaiter(this, void 0, void 0, function () { - var graph_url, graph_fetch, _a, weight_url, weights_data_ab, _b; - return __generator(this, function (_c) { - switch (_c.label) { - case 0: - graph_url = directory + "/graph_" + this.backend + ".json"; - if (this.ignoreCache) { - graph_url += '?t=' + Date.now(); - } - graph_url = WebDNN.transformUrl(graph_url); - return [4 /*yield*/, WebDNN.fetch(graph_url)]; + var _a, descriptor, weightRawArray; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, Promise.all([ + WebDNN.fetch(directory + "/graph_" + this.backendName + ".json", { ignoreCache: this.ignoreCache }) + .then(function (res) { return res.json(); }), + WebDNN.fetch(directory + "/weight_" + this.backendName + ".bin", { ignoreCache: this.ignoreCache }) + .then(function (res) { return WebDNN.readArrayBufferProgressively(res, progressCallback); }) + ])]; case 1: - graph_fetch = _c.sent(); - if (!graph_fetch.ok) { - throw new Error(graph_url + " cannot be loaded"); - } - _a = this; - return [4 /*yield*/, graph_fetch.json()]; - case 2: - _a.descriptor = _c.sent(); + _a = _b.sent(), descriptor = _a[0], weightRawArray = _a[1]; + this.setDescriptor(descriptor); return [4 /*yield*/, this.compile()]; + case 2: + _b.sent(); + return [4 /*yield*/, this.initializeStaticBuffer(weightRawArray)]; case 3: - _c.sent(); - weight_url = directory + "/weight_" + this.backend + ".bin"; - if (this.ignoreCache) { - weight_url += '?t=' + Date.now(); - } - weight_url = WebDNN.transformUrl(weight_url); - _b = WebDNN.readArrayBufferProgressively; - return [4 /*yield*/, WebDNN.fetch(weight_url)]; - case 4: return [4 /*yield*/, _b.apply(void 0, [_c.sent(), progressCallback])]; - case 5: - weights_data_ab = _c.sent(); - return [4 /*yield*/, this.loadWeights(new Uint8Array(weights_data_ab))]; - case 6: - _c.sent(); - return [2 /*return*/]; + _b.sent(); + if (!(this.placeholderContext && this.placeholderContext.isResolved)) return [3 /*break*/, 5]; + return [4 /*yield*/, this.initializeDynamicBuffer()]; + case 4: + _b.sent(); + _b.label = 5; + case 5: return [2 /*return*/]; } }); }); }; DescriptorRunnerFallback.prototype.setDescriptor = function (descriptor) { this.descriptor = descriptor; + // reset + this.placeholderContext = new WebDNN.PlaceholderContext(); + this.placeholderContext.update(descriptor.placeholders); + this.kernelObj = null; + this.variableMap = null; + this.outputViews = null; + this.inputViews = null; + this.staticBuffer = null; + this.dynamicBuffer = null; }; DescriptorRunnerFallback.prototype.compile = function () { return __awaiter(this, void 0, void 0, function () { - var weight_name_alloc, name_2, alloc, variable_name_alloc, name_3, alloc; + var dnn_fallback_kernel; return __generator(this, function (_a) { - this.compileKernel(); - this.rawWeightArray = new Float32Array(this.descriptor.weight_allocation.total_size); - weight_name_alloc = this.descriptor.weight_allocation.allocation; - this.weightArrays = new Map(); - for (name_2 in weight_name_alloc) { - alloc = weight_name_alloc[name_2]; - this.weightArrays.set(name_2, new Float32Array(this.rawWeightArray.buffer, alloc.offset * Float32Array.BYTES_PER_ELEMENT, alloc.size)); - } - this.variableArrays = new Map(); - variable_name_alloc = this.descriptor.variable_allocation.allocation; - for (name_3 in variable_name_alloc) { - alloc = variable_name_alloc[name_3]; - this.variableArrays.set(name_3, new Float32Array(alloc.size)); - } + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + dnn_fallback_kernel = null; + eval(this.descriptor.kernel_source); + this.kernelObj = dnn_fallback_kernel; return [2 /*return*/]; }); }); }; - DescriptorRunnerFallback.prototype.compileKernel = function () { - var dnn_fallback_kernel; - eval(this.descriptor.kernel_source); - this.kernelObj = dnn_fallback_kernel; - }; - DescriptorRunnerFallback.prototype.loadWeights = function (weightsData) { + DescriptorRunnerFallback.prototype.initializeStaticBuffer = function (weightRawArray) { return __awaiter(this, void 0, void 0, function () { - var decoder, _a, _b; + var descriptor, staticBuffer, variableMap, decoder, _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + descriptor = this.descriptor; + staticBuffer = new Float32Array(descriptor.memory_layout.static.size); + this.staticBuffer = staticBuffer; + variableMap = this.variableMap || new Map(); + this.variableMap = variableMap; + Object.entries(descriptor.memory_layout.static.allocations) + .forEach(function (_a) { + var name = _a[0], allocation = _a[1]; + variableMap.set(name, new Float32Array(staticBuffer.buffer, allocation.offset * Float32Array.BYTES_PER_ELEMENT, allocation.size)); + }); decoder = WebDNN.get_weight_decoder(this.descriptor.weight_encoding); - _b = (_a = this.rawWeightArray).set; - return [4 /*yield*/, decoder.decode(weightsData, this.descriptor.weight_allocation)]; + _b = (_a = staticBuffer).set; + return [4 /*yield*/, decoder.decode(new Uint8Array(weightRawArray), this.descriptor.memory_layout)]; case 1: _b.apply(_a, [_c.sent()]); + return [4 /*yield*/, this.getInputViews()]; + case 2: + (_c.sent()) + .filter(function (view) { return !view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer(staticBuffer.buffer); }); + return [4 /*yield*/, this.getOutputViews()]; + case 3: + (_c.sent()) + .filter(function (view) { return !view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer(staticBuffer.buffer); }); + return [2 /*return*/]; + } + }); + }); + }; + DescriptorRunnerFallback.prototype.initializeDynamicBuffer = function () { + return __awaiter(this, void 0, void 0, function () { + var descriptor, placeholderContext, dynamicBuffer, variableMap; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + descriptor = this.descriptor; + placeholderContext = this.placeholderContext; + dynamicBuffer = new Float32Array(placeholderContext.resolve(descriptor.memory_layout.dynamic.size)); + this.dynamicBuffer = dynamicBuffer; + variableMap = this.variableMap || new Map(); + this.variableMap = variableMap; + Object.entries(descriptor.memory_layout.dynamic.allocations) + .forEach(function (_a) { + var name = _a[0], allocation = _a[1]; + variableMap.set(name, new Float32Array(dynamicBuffer.buffer, placeholderContext.resolve(allocation.offset) * Float32Array.BYTES_PER_ELEMENT, placeholderContext.resolve(allocation.size))); + }); + return [4 /*yield*/, this.getInputViews()]; + case 1: + (_a.sent()) + .filter(function (view) { return view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer(dynamicBuffer.buffer); }); + return [4 /*yield*/, this.getOutputViews()]; + case 2: + (_a.sent()) + .filter(function (view) { return view.isDynamic; }) + .forEach(function (view) { return view.setArrayBuffer(dynamicBuffer.buffer); }); + return [2 /*return*/]; + } + }); + }); + }; + DescriptorRunnerFallback.prototype.setPlaceholderValue = function (values) { + return __awaiter(this, void 0, void 0, function () { + var placeholderContext; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!this.placeholderContext) + throw new Error('placeholderContext is not initialized'); + placeholderContext = this.placeholderContext; + placeholderContext.update(values); + if (!placeholderContext.isResolved) + return [2 /*return*/]; + return [4 /*yield*/, this.initializeDynamicBuffer()]; + case 1: + _a.sent(); return [2 /*return*/]; } }); @@ -1077,235 +1548,181 @@ var WebDNN; }; DescriptorRunnerFallback.prototype.run = function () { return __awaiter(this, void 0, void 0, function () { - var _this = this; - var run_entry_date, last_progress_date, i, current_date, elapsed_ms, exec_info, input_arrays, output_arrays, weight_arrays; + var variableMap, placeholderContext, executionInfos, startDate, lastDate, i, currentDate, executionInfo, inputs, outputs; return __generator(this, function (_a) { switch (_a.label) { case 0: - if (!this.inputViews || !this.outputViews) { - throw new Error('getInputViews and getOutputViews must be called prior to run'); - } - run_entry_date = Date.now(); - last_progress_date = Date.now(); + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('placeholderContext is not initialized'); + if (!this.variableMap) + throw new Error('Variable map is not initialized'); + if (!this.staticBuffer) + throw new Error('StaticBuffer map is not initialized'); + if (!this.dynamicBuffer) + throw new Error('DynamicBuffer map is not initialized'); + if (!this.inputViews || !this.outputViews) + throw new Error('getInputViews() and getOutputViews() must be called prior to run'); + variableMap = this.variableMap; + placeholderContext = this.placeholderContext; + executionInfos = this.descriptor.exec_infos + .map(function (executionInfo) { return placeholderContext.resolve(executionInfo); }); + startDate = Date.now(); + lastDate = Date.now(); i = 0; _a.label = 1; case 1: - if (!(i < this.descriptor.exec_infos.length)) return [3 /*break*/, 5]; - current_date = Date.now(); - if (!(current_date - last_progress_date >= 1000)) return [3 /*break*/, 3]; - elapsed_ms = current_date - run_entry_date; - console.log("Processed " + i + "/" + this.descriptor.exec_infos.length + " kernels in " + elapsed_ms + " ms"); - last_progress_date = current_date; - return [4 /*yield*/, this.wait_to_display()]; + if (!(i < executionInfos.length)) return [3 /*break*/, 5]; + currentDate = Date.now(); + if (!(currentDate - lastDate >= 1000)) return [3 /*break*/, 3]; + console.log("Processed " + i + "/" + executionInfos.length + " kernels in " + (currentDate - startDate) + " ms"); + lastDate = currentDate; + return [4 /*yield*/, wait()]; case 2: _a.sent(); _a.label = 3; case 3: - exec_info = this.descriptor.exec_infos[i]; - input_arrays = exec_info.inputs.map(function (name) { return _this.variableArrays.get(name); }); - output_arrays = exec_info.outputs.map(function (name) { return _this.variableArrays.get(name); }); - weight_arrays = exec_info.weights.map(function (name) { return _this.weightArrays.get(name); }); - this.kernelObj[exec_info.entry_func_name](input_arrays, output_arrays, weight_arrays, exec_info.call_option); + executionInfo = executionInfos[i]; + inputs = executionInfo.inputs.map(function (name) { return variableMap.get(name); }); + outputs = executionInfo.outputs.map(function (name) { return variableMap.get(name); }); + this.kernelObj[executionInfo.entry_func_name](inputs, outputs, executionInfo.call_option); _a.label = 4; case 4: i++; return [3 /*break*/, 1]; case 5: - console.log("Processed " + this.descriptor.exec_infos.length + "/" + this.descriptor.exec_infos.length + " kernels in " + (Date.now() - run_entry_date) + " ms"); + console.log("Processed " + executionInfos.length + "/" + executionInfos.length + " kernels in " + (Date.now() - startDate) + " ms"); return [2 /*return*/]; } }); }); }; - DescriptorRunnerFallback.prototype.wait_to_display = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - // let console.log to be displayed, and prevent freeze - return [2 /*return*/, new Promise(function (resolve, reject) { - setTimeout(resolve, 10); - })]; - }); - }); - }; DescriptorRunnerFallback.prototype.getInputViews = function () { - return __awaiter(this, void 0, void 0, function () { - var _this = this; - var views; - return __generator(this, function (_a) { - if (this.inputViews) { - return [2 /*return*/, this.inputViews]; - } - views = this.descriptor.inputs.map(function (name) { return _this.variableArrays.get(name); }); - this.inputViews = views; - return [2 /*return*/, views]; - }); + if (this.inputViews) + return this.inputViews; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + var descriptor = this.descriptor; + var placeholderContext = this.placeholderContext; + this.inputViews = descriptor.inputs.map(function (name) { + var allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + var view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext); + return view; }); + return this.inputViews; }; DescriptorRunnerFallback.prototype.getOutputViews = function () { - return __awaiter(this, void 0, void 0, function () { - var _this = this; - var views; - return __generator(this, function (_a) { - if (this.outputViews) { - return [2 /*return*/, this.outputViews]; - } - views = this.descriptor.outputs.map(function (name) { return _this.variableArrays.get(name); }); - this.outputViews = views; - return [2 /*return*/, views]; - }); + if (this.outputViews) + return this.outputViews; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + var descriptor = this.descriptor; + var placeholderContext = this.placeholderContext; + this.outputViews = descriptor.outputs.map(function (name) { + var allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + var view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext); + return view; }); + return this.outputViews; }; return DescriptorRunnerFallback; - }()); + }(WebDNN.DescriptorRunner)); WebDNN.DescriptorRunnerFallback = DescriptorRunnerFallback; })(WebDNN || (WebDNN = {})); -/// -var WebDNN; -(function (WebDNN) { - var GPUInterfaceFallback = (function () { - function GPUInterfaceFallback(option) { - this.option = option; - } - GPUInterfaceFallback.prototype.init = function (option) { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - return [2 /*return*/]; - }); - }); - }; - GPUInterfaceFallback.prototype.createDescriptorRunner = function () { - return new WebDNN.DescriptorRunnerFallback(); - }; - return GPUInterfaceFallback; - }()); - WebDNN.GPUInterfaceFallback = GPUInterfaceFallback; -})(WebDNN || (WebDNN = {})); -/// -/// -/// -/// +/// +/// +/// +/// var WebDNN; (function (WebDNN) { - var givenBackendOptions; - var tryingBackendOrder; - var loadedBackendName; - function tryInitNext() { + WebDNN.backends = { + 'webgpu': WebDNN.DescriptorRunnerWebGPU, + 'webassembly': WebDNN.DescriptorRunnerWebassembly, + 'fallback': WebDNN.DescriptorRunnerFallback, + }; + WebDNN.DEBUG = false; + function initBackend(backendName, option) { return __awaiter(this, void 0, void 0, function () { - var backend_name, option, gpuif, ex_1; + var runner, ex_1; return __generator(this, function (_a) { switch (_a.label) { case 0: - backend_name = tryingBackendOrder.shift(); - if (!backend_name) { - throw new Error('No backend is available'); - } - option = givenBackendOptions[backend_name]; + if (!(backendName in WebDNN.backends)) + throw new Error("Unknown backend: \"" + backendName + "\""); _a.label = 1; case 1: - _a.trys.push([1, 3, , 5]); - switch (backend_name) { - case 'webgpu': - gpuif = new WebDNN.GPUInterfaceWebGPU(option); - break; - case 'webassembly': - gpuif = new WebDNN.GPUInterfaceWebassembly(option); - break; - case 'fallback': - gpuif = new WebDNN.GPUInterfaceFallback(option); - break; - default: - throw new Error('Unknown backend ' + backend_name); - } - return [4 /*yield*/, gpuif.init()]; + _a.trys.push([1, 3, , 4]); + runner = new WebDNN.backends[backendName](option); + return [4 /*yield*/, runner.init()]; case 2: _a.sent(); - WebDNN.gpu = gpuif; - loadedBackendName = backend_name; - return [3 /*break*/, 5]; + return [3 /*break*/, 4]; case 3: ex_1 = _a.sent(); - console.warn("Failed to initialize " + backend_name + " backend: " + ex_1); - return [4 /*yield*/, tryInitNext()]; - case 4: return [2 /*return*/, _a.sent()]; - case 5: return [2 /*return*/, loadedBackendName]; + console.warn("Failed to initialize " + backendName + " backend: " + ex_1); + return [2 /*return*/, null]; + case 4: return [2 /*return*/, runner]; } }); }); } - function init(backendOrder, backendOptions) { - if (backendOptions === void 0) { backendOptions = {}; } - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (!backendOrder) { - backendOrder = ['webgpu', 'webassembly']; - } - else if (typeof backendOrder === 'string') { - backendOrder = [backendOrder]; - } - givenBackendOptions = backendOptions; - tryingBackendOrder = backendOrder.concat(['fallback']); - return [4 /*yield*/, tryInitNext()]; - case 1: - _a.sent(); - return [2 /*return*/, loadedBackendName]; - } - }); - }); - } - WebDNN.init = init; /** * Prepare backend interface and load model data at once. Internally calls init(). * @param directory URL of directory that contains graph descriptor files (e.g. graph_fallback.json) * @param initOption Initialize option * @return Interface to input/output data and run the model. */ - function prepareAll(directory, initOption) { + function load(directory, initOption) { if (initOption === void 0) { initOption = {}; } return __awaiter(this, void 0, void 0, function () { - var runner, inputViews, outputViews, ex_2; + var backendOrder, backendOptions, backendName, runner, ex_2; return __generator(this, function (_a) { switch (_a.label) { - case 0: return [4 /*yield*/, init(initOption.backendOrder, initOption.backendOptions)]; + case 0: + backendOrder = initOption.backendOrder; + if (!backendOrder) { + backendOrder = ['webgpu', 'webassembly']; + } + else if (typeof backendOrder === 'string') { + backendOrder = [backendOrder]; + } + backendOrder = backendOrder.slice(); + if (backendOrder.indexOf('fallback') === -1) + backendOrder.concat(['fallback']); + backendOptions = initOption.backendOptions || {}; + _a.label = 1; case 1: - _a.sent(); - _a.label = 2; + if (!(backendOrder.length > 0)) return [3 /*break*/, 7]; + backendName = backendOrder.shift(); + return [4 /*yield*/, initBackend(backendName, backendOptions[backendName])]; case 2: - if (!true) return [3 /*break*/, 10]; + runner = _a.sent(); + if (!runner) + return [3 /*break*/, 1]; + runner.ignoreCache = Boolean(initOption.ignoreCache); _a.label = 3; case 3: - _a.trys.push([3, 7, , 9]); - runner = WebDNN.gpu.createDescriptorRunner(); + _a.trys.push([3, 5, , 6]); return [4 /*yield*/, runner.load(directory, initOption.progressCallback)]; case 4: _a.sent(); - return [4 /*yield*/, runner.getInputViews()]; + return [3 /*break*/, 6]; case 5: - inputViews = _a.sent(); - return [4 /*yield*/, runner.getOutputViews()]; - case 6: - outputViews = _a.sent(); - return [2 /*return*/, { - backendName: loadedBackendName, - inputViews: inputViews, - outputViews: outputViews, - run: runner.run.bind(runner) - }]; - case 7: ex_2 = _a.sent(); - console.error("Model loading failed for " + loadedBackendName + " backend. Trying next backend. " + ex_2.message); - return [4 /*yield*/, tryInitNext()]; - case 8: - _a.sent(); - return [3 /*break*/, 9]; - case 9: return [3 /*break*/, 2]; - case 10: return [2 /*return*/]; + console.warn("Model loading failed for " + backendName + " backend. Trying next backend: " + ex_2.message); + return [3 /*break*/, 6]; + case 6: return [2 /*return*/, runner]; + case 7: throw new Error('No backend is available'); } }); }); } - WebDNN.prepareAll = prepareAll; + WebDNN.load = load; })(WebDNN || (WebDNN = {})); var WebDNN; (function (WebDNN) { @@ -1319,6 +1736,7 @@ var WebDNN; */ function argmax(arr, k) { if (k === void 0) { k = 1; } + arr = arr.slice(); var stack = [[0, arr.length]]; var workspace = {}; while (stack.length > 0) { @@ -1365,6 +1783,7 @@ var WebDNN; */ function argmin(arr, k) { if (k === void 0) { k = 1; } + arr = arr.slice(); var stack = [[0, arr.length]]; var workspace = {}; while (stack.length > 0) { diff --git a/dist/webdnn.es5.js.map b/dist/webdnn.es5.js.map index 7feeea816..29d812f1f 100644 --- a/dist/webdnn.es5.js.map +++ b/dist/webdnn.es5.js.map @@ -1 +1 @@ -{"version":3,"file":"webdnn.es5.js","sourceRoot":"","sources":["../src/descriptor_runner/license.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner.ts","../src/descriptor_runner/gpu_interface/gpu_interface.ts","../src/descriptor_runner/buffer/buffer.ts","../src/descriptor_runner/buffer/buffer_webgpu.ts","../src/descriptor_runner/webgpu_handler.ts","../src/descriptor_runner/decoder/weight_decoder_raw.ts","../src/descriptor_runner/decoder/weight_decoder.ts","../src/descriptor_runner/decoder/weight_decoder_eightbit.ts","../src/descriptor_runner/decoder/get_weight_decoder.ts","../src/descriptor_runner/util/dispatch_scheduler.ts","../src/descriptor_runner/fetch.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_webgpu.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_webgpu.ts","../src/descriptor_runner/gpu_interface/gpu_interface_webgpu.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_webassembly.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_webassembly.ts","../src/descriptor_runner/gpu_interface/gpu_interface_webassembly.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_fallback.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_fallback.ts","../src/descriptor_runner/gpu_interface/gpu_interface_fallback.ts","../src/descriptor_runner/webdnn.ts","../src/descriptor_runner/math.ts","../src/descriptor_runner/get_backend_availability.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"webdnn.es5.js","sourceRoot":"","sources":["../src/descriptor_runner/license.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor.ts","../src/descriptor_runner/placeholder.ts","../src/descriptor_runner/memory_layout.ts","../src/descriptor_runner/symbolic_array_buffer_view.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner.ts","../src/descriptor_runner/buffer/buffer.ts","../src/descriptor_runner/buffer/buffer_webgpu.ts","../src/descriptor_runner/webgpu_handler.ts","../src/descriptor_runner/decoder/weight_decoder.ts","../src/descriptor_runner/decoder/weight_decoder_raw.ts","../src/descriptor_runner/decoder/weight_decoder_eightbit.ts","../src/descriptor_runner/decoder/get_weight_decoder.ts","../src/descriptor_runner/util/dispatch_scheduler.ts","../src/descriptor_runner/fetch.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_webgpu.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_webgpu.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_webassembly.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_webassembly.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_fallback.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_fallback.ts","../src/descriptor_runner/webdnn.ts","../src/descriptor_runner/math.ts","../src/descriptor_runner/get_backend_availability.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/webdnn.js b/dist/webdnn.js index 2e0967120..648166a1b 100644 --- a/dist/webdnn.js +++ b/dist/webdnn.js @@ -21,8 +21,159 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +var WebDNN; +(function (WebDNN) { + /** + * PlaceholderContext manages the placeholders + */ + class PlaceholderContext { + constructor(values) { + this.values = {}; + if (values) { + this.update(values); + } + } + get isResolved() { + return Object.values(this.values).every(value => typeof value == 'number'); + } + update(values) { + this.values = Object.assign(this.values, values); + } + resolve(placeholder) { + // Literal value => return itself. + if (typeof placeholder !== 'object') + return placeholder; + // Placeholder object ( { eval: string } ) => resolve + if (Object.keys(placeholder).length == 1 && 'eval' in placeholder) { + if (!this.isResolved) + throw Error(`Not all placeholders are resolved: ${this}`); + return ((placeholders) => eval(placeholder.eval))(this.values); + } + // Array => deep copy + if (placeholder instanceof Array) { + return placeholder.map((value) => this.resolve(value)); + } + // Object => deep copy + return Object.entries(placeholder) + .reduce((result, [key, value]) => { + result[key] = this.resolve(value); + return result; + }, {}); + } + toString() { + return JSON.stringify(this.values); + } + } + WebDNN.PlaceholderContext = PlaceholderContext; +})(WebDNN || (WebDNN = {})); +/// +/// +/// +var WebDNN; +(function (WebDNN) { + class SymbolicArrayBufferView { + constructor(allocation, placeholderContext, ignoreOffsetOnActual = false) { + this.ignoreOffsetOnActual = ignoreOffsetOnActual; + this.allocation = allocation; + if (this.isDynamic) { + if (!placeholderContext) { + throw Error('PlaceholderContext must be required when SymbolicArrayBufferView is initialized as dynamic buffer view.'); + } + } + this.placeholderContext = placeholderContext; + } + setArrayBuffer(arrayBuffer) { + this.arrayBuffer = arrayBuffer; + } + get isDynamic() { + return (typeof this.allocation.offset !== 'number' || typeof this.allocation.size !== 'number'); + } + get offset() { + //TODO + if (this.isDynamic) { + return this.placeholderContext.resolve(this.allocation.offset); + } + else { + return this.allocation.offset; + } + } + get length() { + if (this.isDynamic) { + return this.placeholderContext.resolve(this.allocation.size); + } + else { + return this.allocation.size; + } + } + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array, offset) { + return this.toActual().set(array, offset); + } + } + WebDNN.SymbolicArrayBufferView = SymbolicArrayBufferView; + class SymbolicFloat32Array extends SymbolicArrayBufferView { + toActual() { + if (!this.arrayBuffer) { + throw new Error('Internal buffer for this variable is not set. DescriptorRunner.setPlaceholderValue() have to be called before calling this function.'); + } + return new Float32Array(this.arrayBuffer, this.ignoreOffsetOnActual ? 0 : this.offset * Float32Array.BYTES_PER_ELEMENT, this.length); + } + } + WebDNN.SymbolicFloat32Array = SymbolicFloat32Array; + class SymbolicInt32Array extends SymbolicArrayBufferView { + toActual() { + if (!this.arrayBuffer) { + throw new Error('Internal buffer for this variable is not set. DescriptorRunner.setPlaceholderValue() have to be called before calling this function.'); + } + return new Int32Array(this.arrayBuffer, this.ignoreOffsetOnActual ? 0 : this.offset * Int32Array.BYTES_PER_ELEMENT, this.length); + } + } + WebDNN.SymbolicInt32Array = SymbolicInt32Array; +})(WebDNN || (WebDNN = {})); /// -/// +/// +/// +var WebDNN; +(function (WebDNN) { + /** + * `DescriptorRunner` executes computation based on `GraphDescriptor`. + * + * Typically, DescriptorRunner takes 3 steps to execute DNN model. + * + * 1. Initialize static configurations + * + * Initialize things independent from runtime configuration. + * + * - `init()` + * - `load()` + * + * 2. Initialize dynamic configurations + * + * Initialize things depend on runtime configuration such as batch size, input image size, etc. + * + * - `setPlaceholderValue()` + * - `getInputViews()` + * - `getOutputViews()` + * + * 3. Execute the model + * + * - `run()` + * + * You need to do step 1 and 2 only once. We recommend to call `WebDNN.prepareAll()` instead + * to call `GraphDescriptor#load()` directly. In that method, all procedures in step 1 and 2 are performed. + */ + class DescriptorRunner { + constructor() { + this.descriptor = null; + this.ignoreCache = false; + } + } + WebDNN.DescriptorRunner = DescriptorRunner; +})(WebDNN || (WebDNN = {})); var WebDNN; (function (WebDNN) { /** @@ -97,12 +248,13 @@ var WebDNN; (function (WebDNN) { class WebGPUHandler { async init() { - // asynchronous operation may be added in future - if (!WebGPUHandler.isBrowserSupported) { + if (!WebGPUHandler.isBrowserSupported) throw new Error('This browser does not support WebGPU'); - } - this.context = document.createElement('canvas').getContext('webgpu'); //force cast - this.commandQueue = this.context.createCommandQueue(); + let context = document.createElement('canvas').getContext('webgpu'); + if (!context) + throw new Error('WebGPURenderingContext initialization failed'); + this.context = context; + this.commandQueue = context.createCommandQueue(); this.pipelineStates = new Map(); } createBuffer(arrayBuffer) { @@ -173,23 +325,23 @@ var WebDNN; WebDNN.WebGPUHandler = WebGPUHandler; WebGPUHandler.isBrowserSupported = 'WebGPURenderingContext' in window && 'WebGPUComputeCommandEncoder' in window; })(WebDNN || (WebDNN = {})); +/// /// var WebDNN; (function (WebDNN) { class WeightDecoderRaw { - async decode(data, weight_allocation) { + async decode(data, memory_layout) { return new Float32Array(data.buffer, data.byteOffset, data.byteLength / 4); } } WebDNN.WeightDecoderRaw = WeightDecoderRaw; })(WebDNN || (WebDNN = {})); -/// /// var WebDNN; (function (WebDNN) { class WeightDecoderEightbit { - async decode(data, weight_allocation) { - let dst = new Float32Array(weight_allocation.total_size); + async decode(data, memory_layout) { + let dst = new Float32Array(memory_layout.static.size); let data_view = new DataView(data.buffer, data.byteOffset); let src_offset = 0; while (src_offset < data.length) { @@ -341,10 +493,22 @@ var WebDNN; * Fetch function. WebDNN API use this fetch function instead of original fetch function. * @param input Requested url * @param init Additional information about fetch + * @param init.ignoreCache If true, cache is ignored by appending '?t=(timestamp)' to the end of request url. * @returns Response */ - function fetch(input, init) { - return fetchDelegate(input, init); + async function fetch(input, init) { + if (typeof input == 'string') { + input = transformUrl(input) + ((init && init.ignoreCache) ? '?t=' + Date.now() : ''); + } + else { + input = Object.assign({}, input, { + url: transformUrl(input.url) + ((init && init.ignoreCache) ? '?t=' + Date.now() : '') + }); + } + let res = await fetchDelegate(input, init); + if (!res.ok) + throw new Error(`Fetch returns status code ${res.status}: ${res.statusText}`); + return res; } WebDNN.fetch = fetch; /** @@ -389,88 +553,184 @@ var WebDNN; /// /// /// +/// +/// +const IS_IOS = navigator.userAgent.includes('iPhone'); var WebDNN; (function (WebDNN) { - class DescriptorRunnerWebGPU { - constructor(webGPUHandler) { - this.webGPUHandler = webGPUHandler; - this.ignoreCache = false; - this.backend = 'webgpu'; + class DescriptorRunnerWebGPU extends WebDNN.DescriptorRunner { + //noinspection JSUnusedLocalSymbols + constructor(option) { + super(); + this.backendName = 'webgpu'; + if (!WebDNN.WebGPUHandler.isBrowserSupported) { + throw new Error('WebGPU is not supported on this browser'); + } + } + async init() { + // initialize webgpu, build kernels + this.shaderLanguage = 'metal'; + this.webgpuHandler = new WebDNN.WebGPUHandler(); + await this.webgpuHandler.init(); + WebDNN.BufferWebGPU.init(this.webgpuHandler); + this.initializeBasicKernels(); + } + initializeBasicKernels() { + this.webgpuHandler.loadKernel('kernel void sync(){}', 'basic'); } async load(directory, progressCallback) { - let graph_url = `${directory}/graph_${this.backend}.json`; - if (this.ignoreCache) { - graph_url += '?t=' + Date.now(); - } - graph_url = WebDNN.transformUrl(graph_url); - let graph_fetch = await WebDNN.fetch(graph_url); - if (!graph_fetch.ok) { - throw new Error(`${graph_url} cannot be loaded`); - } - this.descriptor = await graph_fetch.json(); + let [descriptor, weightRawArray] = await Promise.all([ + WebDNN.fetch(`${directory}/graph_${this.backendName}.json`, { ignoreCache: this.ignoreCache }) + .then(res => res.json()), + WebDNN.fetch(`${directory}/weight_${this.backendName}.bin`, { ignoreCache: this.ignoreCache }) + .then(res => WebDNN.readArrayBufferProgressively(res, progressCallback)) + ]); + await this.setDescriptor(descriptor); await this.compile(); - let weight_url = `${directory}/weight_${this.backend}.bin`; - if (this.ignoreCache) { - weight_url += '?t=' + Date.now(); - } - weight_url = WebDNN.transformUrl(weight_url); - let weights_data_ab = await WebDNN.readArrayBufferProgressively(await WebDNN.fetch(weight_url, progressCallback), progressCallback); - await this.loadWeights(new Uint8Array(weights_data_ab)); - } - setDescriptor(descriptor) { + await this.initializeStaticBuffer(weightRawArray); + await this.initializeMetaBuffers(); + await this.setPlaceholderValue({ + '__MAX_THREADS_PER_THREADGROUP__': IS_IOS ? 512 : 512 + }); + if (this.placeholderContext && this.placeholderContext.isResolved) + await this.initializeDynamicBuffer(); + } + async initializeStaticBuffer(weightRawArray) { + if (!this.descriptor) + throw Error("GraphDescriptor is not loaded."); + let descriptor = this.descriptor; + let staticBuffer = new WebDNN.BufferWebGPU(descriptor.memory_layout.static.size * Float32Array.BYTES_PER_ELEMENT); + this.staticBuffer = staticBuffer; + let decoder = WebDNN.get_weight_decoder(descriptor.weight_encoding); + await staticBuffer.write(await decoder.decode(new Uint8Array(weightRawArray), descriptor.memory_layout)); + (await this.getInputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer(staticBuffer.bufferView.buffer)); + (await this.getOutputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer(staticBuffer.bufferView.buffer)); + } + async initializeMetaBuffers() { + if (!this.descriptor) + throw Error("GraphDescriptor is not loaded."); + this.metaBuffers = await Promise.all(this.descriptor.exec_infos.map(async (executionInfo) => { + let buffer = new WebDNN.BufferWebGPU(executionInfo.meta_buffer.length * Int32Array.BYTES_PER_ELEMENT); + await buffer.write(new Uint8Array(executionInfo.meta_buffer)); + return buffer; + })); + } + async initializeDynamicBuffer() { + if (!this.descriptor) + throw Error("GraphDescriptor is not loaded."); + if (!this.placeholderContext) + throw Error("PlaceholderContext is not initialized."); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + let dynamicBufferSize = placeholderContext.resolve(descriptor.memory_layout.dynamic.size); + let dynamicBuffer = new WebDNN.BufferWebGPU(dynamicBufferSize * Float32Array.BYTES_PER_ELEMENT); + this.dynamicBuffer = dynamicBuffer; + (await this.getInputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer(dynamicBuffer.bufferView.buffer)); + (await this.getOutputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer(dynamicBuffer.bufferView.buffer)); + } + async setDescriptor(descriptor) { this.descriptor = descriptor; + //reset all datum depend on old descriptor + this.staticBuffer = null; + this.dynamicBuffer = null; + this.metaBuffers = null; + this.placeholderContext = new WebDNN.PlaceholderContext(descriptor.placeholders); + this.executionInfos = descriptor.exec_infos; } async compile() { - this.webGPUHandler.loadKernel(this.descriptor.kernel_source, 'descriptor'); - this.weightMat = new WebDNN.BufferWebGPU(this.descriptor.weight_allocation.total_size * Float32Array.BYTES_PER_ELEMENT); - this.dataMat = new WebDNN.BufferWebGPU(this.descriptor.variable_allocation.total_size * Float32Array.BYTES_PER_ELEMENT); - this.metaBufferGPUBuffers = []; - for (let i = 0; i < this.descriptor.exec_infos.length; i++) { - let exec_info = this.descriptor.exec_infos[i]; - let buf = new WebDNN.BufferWebGPU(exec_info.meta_buffer.length * Float32Array.BYTES_PER_ELEMENT); - await buf.write(new Uint8Array(exec_info.meta_buffer)); - this.metaBufferGPUBuffers.push(buf); - } - } - async loadWeights(weightsData) { - let decoder = WebDNN.get_weight_decoder(this.descriptor.weight_encoding); - await this.weightMat.write(await decoder.decode(weightsData, this.descriptor.weight_allocation)); + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + this.webgpuHandler.loadKernel(this.descriptor.kernel_source, 'descriptor'); + } + async setPlaceholderValue(values) { + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized.'); + let placeholderContext = this.placeholderContext; + placeholderContext.update(values); + if (!placeholderContext.isResolved) + return; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.metaBuffers) + throw new Error('MetaBuffers are not initialized'); + let descriptor = this.descriptor; + let metaBuffers = this.metaBuffers; + await this.initializeDynamicBuffer(); + // resolve placeholders in execution info + this.executionInfos = await Promise.all(descriptor.exec_infos.map(async (executionInfo, i) => { + // resolve placeholders in meta buffer + let bufferView = new Int32Array(metaBuffers[i].bufferView.buffer); + for (let unresolved_value of executionInfo.unresolved_value_list) { + bufferView[unresolved_value.offset] = placeholderContext.resolve(unresolved_value.placeholder); + } + return placeholderContext.resolve(executionInfo); + })); } - async getInputViews() { - if (this.inputViews) { + getInputViews() { + if (this.inputViews) return this.inputViews; - } - let views = []; - for (let i = 0; i < this.descriptor.inputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.inputs[i]]; - views.push(this.dataMat.getWriteView(var_alloc.offset, var_alloc.size, Float32Array)); - } - this.inputViews = views; - return views; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + this.inputViews = descriptor.inputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext); + return view; + }); + return this.inputViews; } - async getOutputViews() { - if (this.outputViews) { + getOutputViews() { + if (this.outputViews) return this.outputViews; - } - let views = []; - for (let i = 0; i < this.descriptor.outputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.outputs[i]]; - views.push(this.dataMat.getReadView(var_alloc.offset, var_alloc.size, Float32Array)); - } - this.outputViews = views; - return views; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + this.outputViews = descriptor.outputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext); + return view; + }); + return this.outputViews; } async run() { - if (!this.inputViews || !this.outputViews) { + if (!this.executionInfos) + throw new Error('ExecutionInfos is not loaded'); + if (!this.inputViews || !this.outputViews) throw new Error('getInputViews and getOutputViews must be called prior to run'); - } - if (window['PROFILE']) { + if (!this.staticBuffer) + throw new Error('StaticBuffer is not initialized'); + if (!this.dynamicBuffer) + throw new Error('DynamicBuffer is not initialized'); + if (!this.metaBuffers) + throw new Error('MetaBuffer is not initialized'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + if (!this.placeholderContext.isResolved) + throw new Error(`Not all placeholders are resolved: ${this.placeholderContext}`); + let staticBuffer = this.staticBuffer; + let dynamicBuffer = this.dynamicBuffer; + let metaBuffers = this.metaBuffers; + if (WebDNN.DEBUG) { let records = []; let totalElapsedTime = 0; - for (let i = 0; i < this.descriptor.exec_infos.length; i++) { - let exec_info = this.descriptor.exec_infos[i]; + for (let i = 0; i < this.executionInfos.length; i++) { + let exec_info = this.executionInfos[i]; let start = performance.now(); - await this.webGPUHandler.executeSinglePipelineState('descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, [this.weightMat, this.dataMat, this.metaBufferGPUBuffers[i]], true); + await this.webgpuHandler.executeSinglePipelineState('descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, [staticBuffer, dynamicBuffer, metaBuffers[i]], true); let elapsedTime = performance.now() - start; records.push({ 'Kernel': exec_info.entry_func_name, @@ -496,10 +756,10 @@ var WebDNN; } else { let complete_promise = null; - for (let i = 0; i < this.descriptor.exec_infos.length; i++) { - let exec_info = this.descriptor.exec_infos[i]; - let is_last = i == this.descriptor.exec_infos.length - 1; - complete_promise = this.webGPUHandler.executeSinglePipelineState('descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, [this.weightMat, this.dataMat, this.metaBufferGPUBuffers[i]], is_last); + for (let i = 0; i < this.executionInfos.length; i++) { + let exec_info = this.executionInfos[i]; + let is_last = i == this.executionInfos.length - 1; + complete_promise = this.webgpuHandler.executeSinglePipelineState('descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, [staticBuffer, dynamicBuffer, metaBuffers[i]], is_last); } await complete_promise; //wait to finish final kernel } @@ -507,33 +767,6 @@ var WebDNN; } WebDNN.DescriptorRunnerWebGPU = DescriptorRunnerWebGPU; })(WebDNN || (WebDNN = {})); -/// -var WebDNN; -(function (WebDNN) { - class GPUInterfaceWebGPU { - constructor(option) { - this.option = option; - if (!WebDNN.WebGPUHandler.isBrowserSupported) { - throw new Error('WebGPU is not supported on this browser'); - } - } - async init() { - // initialize webgpu, build kernels - this.shaderLanguage = 'metal'; - this.webgpuHandler = new WebDNN.WebGPUHandler(); - await this.webgpuHandler.init(); - WebDNN.BufferWebGPU.init(this.webgpuHandler); - this.init_basic_kernels(); - } - init_basic_kernels() { - this.webgpuHandler.loadKernel('kernel void sync(){}', 'basic'); - } - createDescriptorRunner() { - return new WebDNN.DescriptorRunnerWebGPU(this.webgpuHandler); - } - } - WebDNN.GPUInterfaceWebGPU = GPUInterfaceWebGPU; -})(WebDNN || (WebDNN = {})); /// /// /// @@ -542,24 +775,28 @@ var WebDNN; /// var WebDNN; (function (WebDNN) { - class DescriptorRunnerWebassembly { - constructor() { - this.ignoreCache = false; - this.backend = 'webassembly'; + class DescriptorRunnerWebassembly extends WebDNN.DescriptorRunner { + constructor(option) { + super(); + this.backendName = 'webassembly'; this.worker_promise_reject_func = null; this.worker_initial_error = null; - } - async load(directory, progressCallback) { - let graph_url = `${directory}/graph_${this.backend}.json`; - if (this.ignoreCache) { - graph_url += '?t=' + Date.now(); + if (typeof Worker === 'undefined') { + throw new Error('WebWorker is needed for WebAssembly backend'); } - graph_url = WebDNN.transformUrl(graph_url); - let graph_fetch = await WebDNN.fetch(graph_url); - if (!graph_fetch.ok) { - throw new Error(`${graph_url} cannot be loaded`); + if (typeof WebAssembly !== 'object') { + console.warn('WebAssembly is not supported on this browser, trying to use asm.js code'); } + } + init() { + //nothing to do + return Promise.resolve(); + } + async load(directory, progressCallback) { + let graph_url = `${directory}/graph_${this.backendName}.json`; + let graph_fetch = await WebDNN.fetch(graph_url, { ignoreCache: this.ignoreCache }); this.descriptor = await graph_fetch.json(); + this.placeholderContext = new WebDNN.PlaceholderContext(this.descriptor.placeholders); // for browsers which does not support wasm, try asm.js code let kernel_backend = typeof WebAssembly === 'object' ? 'webassembly' : 'asmjs'; let worker_entry_js_path = `${directory}/kernels_${kernel_backend}.js`; @@ -569,21 +806,69 @@ var WebDNN; worker_entry_js_path = WebDNN.transformUrl(worker_entry_js_path); this.worker_entry_js_path = worker_entry_js_path; await this.compile(); - let weight_url = `${directory}/weight_${this.backend}.bin`; - if (this.ignoreCache) { - weight_url += '?t=' + Date.now(); - } - weight_url = WebDNN.transformUrl(weight_url); - let weights_data_ab = await WebDNN.readArrayBufferProgressively(await WebDNN.fetch(weight_url), progressCallback); + let weight_url = `${directory}/weight_${this.backendName}.bin`; + let weight_fetch = await WebDNN.fetch(weight_url, { ignoreCache: this.ignoreCache }); + let weights_data_ab = await WebDNN.readArrayBufferProgressively(weight_fetch, progressCallback); await this.loadWeights(new Uint8Array(weights_data_ab)); - } - setDescriptor(descriptor) { - this.descriptor = descriptor; + //assign buffer to input/output buffer view + (await this.getInputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer((new Float32Array(view.length)).buffer)); + (await this.getOutputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer((new Float32Array(view.length)).buffer)); + } + async setPlaceholderValue(values) { + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized.'); + let placeholderContext = this.placeholderContext; + placeholderContext.update(values); + if (!placeholderContext.isResolved) + return; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + let descriptor = this.descriptor; + let unresolvedValueLists = descriptor.unresolved_value_lists; + let metaBufferFillList = []; + for (let kernel_order = 0; kernel_order < unresolvedValueLists.length; kernel_order++) { + let unresolvedValueList = unresolvedValueLists[kernel_order]; + unresolvedValueList.forEach((offset_placeholder) => { + let resolved_value = placeholderContext.resolve(offset_placeholder.placeholder); + metaBufferFillList.push(kernel_order, offset_placeholder.offset, resolved_value); + }); + } + (await this.getInputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer((new Float32Array(view.length)).buffer)); + (await this.getOutputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer((new Float32Array(view.length)).buffer)); + let dynamicBufferSize = this.placeholderContext.resolve(this.descriptor.memory_layout.dynamic.size); + await this.setPlaceholderValueWorker(dynamicBufferSize, new Int32Array(metaBufferFillList)); + } + setPlaceholderValueWorker(dynamicBufferSize, metaBufferFillArray) { + if (!this.worker) + throw Error("Worker is not initialized"); + let worker = this.worker; + return new Promise((resolve, reject) => { + worker.onmessage = (event) => { + if (event.data === 0) { + resolve(); + } + else { + console.log(event.data); + worker.terminate(); + reject(new Error(event.data)); + } + }; + worker.postMessage({ type: 'set_dynamic_buffer', size: dynamicBufferSize, data: metaBufferFillArray }); + }); } compile() { - this.worker = new Worker(this.worker_entry_js_path); - this.worker.onerror = (event) => { - console.error('Worker Exception: ' + event.message); + let worker = new Worker(this.worker_entry_js_path); + worker.onerror = (event) => { + console.error(event); + // console.error('Worker Exception: ' + event.message); if (this.worker_promise_reject_func) { this.worker_promise_reject_func(event); } @@ -592,327 +877,373 @@ var WebDNN; } }; let promise = new Promise((resolve, reject) => { - if (this.worker_initial_error) { - // occurs when this.worker_entry_js_path is 404 - reject(this.worker_initial_error); - return; - } + // occurs when this.worker_entry_js_path is 404 + if (this.worker_initial_error) + return reject(this.worker_initial_error); this.worker_promise_reject_func = reject; - this.worker.onmessage = (event) => { + worker.onmessage = (event) => { if (event.data === 0) { resolve(); } else { - this.worker.terminate(); + console.error(event.data); + worker.terminate(); reject(new Error(event.data)); } }; - //this.worker.postMessage({ type: 'init' }); }); + this.worker = worker; return promise; } async loadWeights(weightsData) { + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.worker) + throw new Error('Worker is not initialized'); let decoder = WebDNN.get_weight_decoder(this.descriptor.weight_encoding); - let weight_data = await decoder.decode(weightsData, this.descriptor.weight_allocation); + let weight_data = await decoder.decode(weightsData, this.descriptor.memory_layout); + let worker = this.worker; let promise = new Promise((resolve, reject) => { this.worker_promise_reject_func = reject; - this.worker.onmessage = (event) => { + worker.onmessage = (event) => { if (event.data === 0) { resolve(); } else { - this.worker.terminate(); + console.log(event.data); + worker.terminate(); reject(new Error(event.data)); } }; - this.worker.postMessage({ type: 'weight', data: weight_data }); + worker.postMessage({ type: 'weight', data: weight_data }); }); return promise; } - async getInputViews() { - if (this.inputViews) { + getInputViews() { + if (this.inputViews) return this.inputViews; - } - let views = []; - for (let i = 0; i < this.descriptor.inputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.inputs[i]]; - views.push(new Float32Array(var_alloc.size)); - } - this.inputViews = views; - return views; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + this.inputViews = descriptor.inputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext, true); + return view; + }); + return this.inputViews; } - async getOutputViews() { - if (this.outputViews) { + getOutputViews() { + if (this.outputViews) return this.outputViews; - } - let views = []; - for (let i = 0; i < this.descriptor.outputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.outputs[i]]; - views.push(new Float32Array(var_alloc.size)); - } - this.outputViews = views; - return views; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + this.outputViews = descriptor.outputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + // buffer for SymbolicFloat32Array is dedicated for IO, since computation is performed on separate memory space. + let view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext, true); + return view; + }); + return this.outputViews; } async run() { - if (!this.inputViews || !this.outputViews) { + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.inputViews || !this.outputViews) throw new Error('getInputViews and getOutputViews must be called prior to run'); - } + if (!this.worker) + throw new Error('Worker is not initialized'); + let descriptor = this.descriptor; + let worker = this.worker; + let inputViews = this.inputViews; + let outputViews = this.outputViews; let promise = new Promise((resolve, reject) => { // TODO: better way not to generate function on every run this.worker_promise_reject_func = reject; - this.worker.onmessage = (event) => { + worker.onmessage = (event) => { if (Array.isArray(event.data)) { for (let i = 0; i < event.data.length; i++) { - this.outputViews[i].set(event.data[i]); + outputViews[i].set(event.data[i]); } resolve(); } else { - this.worker.terminate(); + console.log(event.data); + worker.terminate(); reject(new Error(event.data)); } }; + let allocations = [descriptor.memory_layout.static.allocations, descriptor.memory_layout.dynamic.allocations]; let inputs = []; - for (let i = 0; i < this.descriptor.inputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.inputs[i]]; - inputs.push({ offset: var_alloc.offset, size: var_alloc.size, data: this.inputViews[i] }); + for (let i = 0; i < descriptor.inputs.length; i++) { + for (let allocation_space = 0; allocation_space < 2; allocation_space++) { + let var_alloc = allocations[allocation_space][descriptor.inputs[i]]; + if (var_alloc) { + let symAb = inputViews[i]; + inputs.push({ + space: allocation_space, + offset: symAb.offset, + size: symAb.length, + data: symAb.toActual() + }); + break; + } + } } let outputs = []; - for (let i = 0; i < this.descriptor.outputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.outputs[i]]; - outputs.push({ offset: var_alloc.offset, size: var_alloc.size }); + for (let i = 0; i < descriptor.outputs.length; i++) { + for (let allocation_space = 0; allocation_space < 2; allocation_space++) { + let var_alloc = allocations[allocation_space][descriptor.outputs[i]]; + if (var_alloc) { + let symAb = outputViews[i]; + outputs.push({ space: allocation_space, offset: symAb.offset, size: symAb.length }); + break; + } + } } - this.worker.postMessage({ type: 'run', inputs: inputs, outputs: outputs }); + worker.postMessage({ type: 'run', inputs: inputs, outputs: outputs }); }); return promise; } } WebDNN.DescriptorRunnerWebassembly = DescriptorRunnerWebassembly; })(WebDNN || (WebDNN = {})); -/// -/// -var WebDNN; -(function (WebDNN) { - class GPUInterfaceWebassembly { - constructor(option) { - this.option = option; - if (typeof Worker === 'undefined') { - throw new Error('WebWorker is needed for WebAssembly backend'); - } - if (typeof WebAssembly !== 'object') { - console.warn('WebAssembly is not supported on this browser, trying to use asm.js code'); - } - } - async init() { - } - createDescriptorRunner() { - return new WebDNN.DescriptorRunnerWebassembly(); - } - } - WebDNN.GPUInterfaceWebassembly = GPUInterfaceWebassembly; -})(WebDNN || (WebDNN = {})); /// /// /// +function wait(duration = 10) { + // let console.log to be displayed, and prevent freeze + return new Promise(resolve => setTimeout(resolve, duration)); +} var WebDNN; (function (WebDNN) { - class DescriptorRunnerFallback { - constructor() { - this.ignoreCache = false; - this.backend = 'fallback'; + class DescriptorRunnerFallback extends WebDNN.DescriptorRunner { + constructor(option) { + super(); + this.backendName = 'fallback'; + } + async init() { + //nothing to do } async load(directory, progressCallback) { - let graph_url = `${directory}/graph_${this.backend}.json`; - if (this.ignoreCache) { - graph_url += '?t=' + Date.now(); - } - graph_url = WebDNN.transformUrl(graph_url); - let graph_fetch = await WebDNN.fetch(graph_url); - if (!graph_fetch.ok) { - throw new Error(`${graph_url} cannot be loaded`); - } - this.descriptor = await graph_fetch.json(); + let [descriptor, weightRawArray] = await Promise.all([ + WebDNN.fetch(`${directory}/graph_${this.backendName}.json`, { ignoreCache: this.ignoreCache }) + .then(res => res.json()), + WebDNN.fetch(`${directory}/weight_${this.backendName}.bin`, { ignoreCache: this.ignoreCache }) + .then(res => WebDNN.readArrayBufferProgressively(res, progressCallback)) + ]); + this.setDescriptor(descriptor); await this.compile(); - let weight_url = `${directory}/weight_${this.backend}.bin`; - if (this.ignoreCache) { - weight_url += '?t=' + Date.now(); - } - weight_url = WebDNN.transformUrl(weight_url); - let weights_data_ab = await WebDNN.readArrayBufferProgressively(await WebDNN.fetch(weight_url), progressCallback); - await this.loadWeights(new Uint8Array(weights_data_ab)); + await this.initializeStaticBuffer(weightRawArray); + if (this.placeholderContext && this.placeholderContext.isResolved) + await this.initializeDynamicBuffer(); } setDescriptor(descriptor) { this.descriptor = descriptor; + // reset + this.placeholderContext = new WebDNN.PlaceholderContext(); + this.placeholderContext.update(descriptor.placeholders); + this.kernelObj = null; + this.variableMap = null; + this.outputViews = null; + this.inputViews = null; + this.staticBuffer = null; + this.dynamicBuffer = null; } async compile() { - this.compileKernel(); - this.rawWeightArray = new Float32Array(this.descriptor.weight_allocation.total_size); - let weight_name_alloc = this.descriptor.weight_allocation.allocation; - this.weightArrays = new Map(); - for (let name in weight_name_alloc) { - let alloc = weight_name_alloc[name]; - this.weightArrays.set(name, new Float32Array(this.rawWeightArray.buffer, alloc.offset * Float32Array.BYTES_PER_ELEMENT, alloc.size)); - } - this.variableArrays = new Map(); - let variable_name_alloc = this.descriptor.variable_allocation.allocation; - for (let name in variable_name_alloc) { - let alloc = variable_name_alloc[name]; - this.variableArrays.set(name, new Float32Array(alloc.size)); - } - } - compileKernel() { - var dnn_fallback_kernel; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + let dnn_fallback_kernel = null; eval(this.descriptor.kernel_source); this.kernelObj = dnn_fallback_kernel; } - async loadWeights(weightsData) { - // when weight format becomes not flat array (such as using quantization), the interface should be changed + async initializeStaticBuffer(weightRawArray) { + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + let descriptor = this.descriptor; + let staticBuffer = new Float32Array(descriptor.memory_layout.static.size); + this.staticBuffer = staticBuffer; + let variableMap = this.variableMap || new Map(); + this.variableMap = variableMap; + Object.entries(descriptor.memory_layout.static.allocations) + .forEach(([name, allocation]) => { + variableMap.set(name, new Float32Array(staticBuffer.buffer, allocation.offset * Float32Array.BYTES_PER_ELEMENT, allocation.size)); + }); let decoder = WebDNN.get_weight_decoder(this.descriptor.weight_encoding); - this.rawWeightArray.set(await decoder.decode(weightsData, this.descriptor.weight_allocation)); + staticBuffer.set(await decoder.decode(new Uint8Array(weightRawArray), this.descriptor.memory_layout)); + (await this.getInputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer(staticBuffer.buffer)); + (await this.getOutputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer(staticBuffer.buffer)); + } + async initializeDynamicBuffer() { + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + let dynamicBuffer = new Float32Array(placeholderContext.resolve(descriptor.memory_layout.dynamic.size)); + this.dynamicBuffer = dynamicBuffer; + let variableMap = this.variableMap || new Map(); + this.variableMap = variableMap; + Object.entries(descriptor.memory_layout.dynamic.allocations) + .forEach(([name, allocation]) => { + variableMap.set(name, new Float32Array(dynamicBuffer.buffer, placeholderContext.resolve(allocation.offset) * Float32Array.BYTES_PER_ELEMENT, placeholderContext.resolve(allocation.size))); + }); + (await this.getInputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer(dynamicBuffer.buffer)); + (await this.getOutputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer(dynamicBuffer.buffer)); + } + async setPlaceholderValue(values) { + if (!this.placeholderContext) + throw new Error('placeholderContext is not initialized'); + let placeholderContext = this.placeholderContext; + placeholderContext.update(values); + if (!placeholderContext.isResolved) + return; + await this.initializeDynamicBuffer(); } async run() { - if (!this.inputViews || !this.outputViews) { - throw new Error('getInputViews and getOutputViews must be called prior to run'); - } - let run_entry_date = Date.now(); - let last_progress_date = Date.now(); //in milliseconds - for (let i = 0; i < this.descriptor.exec_infos.length; i++) { - let current_date = Date.now(); - if (current_date - last_progress_date >= 1000) { - let elapsed_ms = current_date - run_entry_date; - console.log(`Processed ${i}/${this.descriptor.exec_infos.length} kernels in ${elapsed_ms} ms`); - last_progress_date = current_date; - await this.wait_to_display(); + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('placeholderContext is not initialized'); + if (!this.variableMap) + throw new Error('Variable map is not initialized'); + if (!this.staticBuffer) + throw new Error('StaticBuffer map is not initialized'); + if (!this.dynamicBuffer) + throw new Error('DynamicBuffer map is not initialized'); + if (!this.inputViews || !this.outputViews) + throw new Error('getInputViews() and getOutputViews() must be called prior to run'); + let variableMap = this.variableMap; + let placeholderContext = this.placeholderContext; + let executionInfos = this.descriptor.exec_infos + .map(executionInfo => placeholderContext.resolve(executionInfo)); + let startDate = Date.now(); + let lastDate = Date.now(); + for (let i = 0; i < executionInfos.length; i++) { + let currentDate = Date.now(); + if (currentDate - lastDate >= 1000) { + console.log(`Processed ${i}/${executionInfos.length} kernels in ${currentDate - startDate} ms`); + lastDate = currentDate; + await wait(); } - let exec_info = this.descriptor.exec_infos[i]; - let input_arrays = exec_info.inputs.map((name) => this.variableArrays.get(name)); - let output_arrays = exec_info.outputs.map((name) => this.variableArrays.get(name)); - let weight_arrays = exec_info.weights.map((name) => this.weightArrays.get(name)); - this.kernelObj[exec_info.entry_func_name](input_arrays, output_arrays, weight_arrays, exec_info.call_option); + let executionInfo = executionInfos[i]; + let inputs = executionInfo.inputs.map((name) => variableMap.get(name)); + let outputs = executionInfo.outputs.map((name) => variableMap.get(name)); + this.kernelObj[executionInfo.entry_func_name](inputs, outputs, executionInfo.call_option); } - console.log(`Processed ${this.descriptor.exec_infos.length}/${this.descriptor.exec_infos.length} kernels in ${Date.now() - run_entry_date} ms`); + console.log(`Processed ${executionInfos.length}/${executionInfos.length} kernels in ${Date.now() - startDate} ms`); } - async wait_to_display() { - // let console.log to be displayed, and prevent freeze - return new Promise(function (resolve, reject) { - setTimeout(resolve, 10); - }); - } - async getInputViews() { - if (this.inputViews) { + getInputViews() { + if (this.inputViews) return this.inputViews; - } - let views = this.descriptor.inputs.map((name) => this.variableArrays.get(name)); - this.inputViews = views; - return views; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + this.inputViews = descriptor.inputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext); + return view; + }); + return this.inputViews; } - async getOutputViews() { - if (this.outputViews) { + getOutputViews() { + if (this.outputViews) return this.outputViews; - } - let views = this.descriptor.outputs.map((name) => this.variableArrays.get(name)); - this.outputViews = views; - return views; + if (!this.descriptor) + throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) + throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + this.outputViews = descriptor.outputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new WebDNN.SymbolicFloat32Array(allocation, placeholderContext); + return view; + }); + return this.outputViews; } } WebDNN.DescriptorRunnerFallback = DescriptorRunnerFallback; })(WebDNN || (WebDNN = {})); -/// +/// +/// +/// +/// var WebDNN; (function (WebDNN) { - class GPUInterfaceFallback { - constructor(option) { - this.option = option; - } - async init(option) { - } - createDescriptorRunner() { - return new WebDNN.DescriptorRunnerFallback(); - } - } - WebDNN.GPUInterfaceFallback = GPUInterfaceFallback; -})(WebDNN || (WebDNN = {})); -/// -/// -/// -/// -var WebDNN; -(function (WebDNN) { - let givenBackendOptions; - let tryingBackendOrder; - let loadedBackendName; - async function tryInitNext() { - let backend_name = tryingBackendOrder.shift(); - if (!backend_name) { - throw new Error('No backend is available'); - } - let option = givenBackendOptions[backend_name]; - let gpuif; + WebDNN.backends = { + 'webgpu': WebDNN.DescriptorRunnerWebGPU, + 'webassembly': WebDNN.DescriptorRunnerWebassembly, + 'fallback': WebDNN.DescriptorRunnerFallback, + }; + WebDNN.DEBUG = false; + async function initBackend(backendName, option) { + if (!(backendName in WebDNN.backends)) + throw new Error(`Unknown backend: "${backendName}"`); + let runner; try { - switch (backend_name) { - case 'webgpu': - gpuif = new WebDNN.GPUInterfaceWebGPU(option); - break; - case 'webassembly': - gpuif = new WebDNN.GPUInterfaceWebassembly(option); - break; - case 'fallback': - gpuif = new WebDNN.GPUInterfaceFallback(option); - break; - default: - throw new Error('Unknown backend ' + backend_name); - } - await gpuif.init(); - WebDNN.gpu = gpuif; - loadedBackendName = backend_name; + runner = new WebDNN.backends[backendName](option); + await runner.init(); } catch (ex) { - console.warn(`Failed to initialize ${backend_name} backend: ${ex}`); - return await tryInitNext(); - } - return loadedBackendName; - } - async function init(backendOrder, backendOptions = {}) { - if (!backendOrder) { - backendOrder = ['webgpu', 'webassembly']; + console.warn(`Failed to initialize ${backendName} backend: ${ex}`); + return null; } - else if (typeof backendOrder === 'string') { - backendOrder = [backendOrder]; - } - givenBackendOptions = backendOptions; - tryingBackendOrder = backendOrder.concat(['fallback']); - await tryInitNext(); - return loadedBackendName; + return runner; } - WebDNN.init = init; /** * Prepare backend interface and load model data at once. Internally calls init(). * @param directory URL of directory that contains graph descriptor files (e.g. graph_fallback.json) * @param initOption Initialize option * @return Interface to input/output data and run the model. */ - async function prepareAll(directory, initOption = {}) { - await init(initOption.backendOrder, initOption.backendOptions); - while (true) { + async function load(directory, initOption = {}) { + let backendOrder = initOption.backendOrder; + if (!backendOrder) { + backendOrder = ['webgpu', 'webassembly']; + } + else if (typeof backendOrder === 'string') { + backendOrder = [backendOrder]; + } + backendOrder = backendOrder.slice(); + if (backendOrder.indexOf('fallback') === -1) + backendOrder.concat(['fallback']); + let backendOptions = initOption.backendOptions || {}; + while (backendOrder.length > 0) { + let backendName = backendOrder.shift(); + let runner = await initBackend(backendName, backendOptions[backendName]); + if (!runner) + continue; + runner.ignoreCache = Boolean(initOption.ignoreCache); try { - let runner = WebDNN.gpu.createDescriptorRunner(); await runner.load(directory, initOption.progressCallback); - let inputViews = await runner.getInputViews(); - let outputViews = await runner.getOutputViews(); - return { - backendName: loadedBackendName, - inputViews: inputViews, - outputViews: outputViews, - run: runner.run.bind(runner) - }; } catch (ex) { - console.error(`Model loading failed for ${loadedBackendName} backend. Trying next backend. ${ex.message}`); - await tryInitNext(); + console.warn(`Model loading failed for ${backendName} backend. Trying next backend: ${ex.message}`); } + return runner; } + throw new Error('No backend is available'); } - WebDNN.prepareAll = prepareAll; + WebDNN.load = load; })(WebDNN || (WebDNN = {})); var WebDNN; (function (WebDNN) { @@ -925,6 +1256,7 @@ var WebDNN; * @returns {number[]} indices of top-K largest samples */ function argmax(arr, k = 1) { + arr = arr.slice(); let stack = [[0, arr.length]]; let workspace = {}; while (stack.length > 0) { @@ -970,6 +1302,7 @@ var WebDNN; * @returns {number[]} indices of top-K smallest samples */ function argmin(arr, k = 1) { + arr = arr.slice(); let stack = [[0, arr.length]]; let workspace = {}; while (stack.length > 0) { diff --git a/dist/webdnn.js.map b/dist/webdnn.js.map index 50ab00d9c..919b1ae12 100644 --- a/dist/webdnn.js.map +++ b/dist/webdnn.js.map @@ -1 +1 @@ -{"version":3,"file":"webdnn.js","sourceRoot":"","sources":["../src/descriptor_runner/license.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner.ts","../src/descriptor_runner/gpu_interface/gpu_interface.ts","../src/descriptor_runner/buffer/buffer.ts","../src/descriptor_runner/buffer/buffer_webgpu.ts","../src/descriptor_runner/webgpu_handler.ts","../src/descriptor_runner/decoder/weight_decoder_raw.ts","../src/descriptor_runner/decoder/weight_decoder.ts","../src/descriptor_runner/decoder/weight_decoder_eightbit.ts","../src/descriptor_runner/decoder/get_weight_decoder.ts","../src/descriptor_runner/util/dispatch_scheduler.ts","../src/descriptor_runner/fetch.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_webgpu.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_webgpu.ts","../src/descriptor_runner/gpu_interface/gpu_interface_webgpu.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_webassembly.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_webassembly.ts","../src/descriptor_runner/gpu_interface/gpu_interface_webassembly.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_fallback.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_fallback.ts","../src/descriptor_runner/gpu_interface/gpu_interface_fallback.ts","../src/descriptor_runner/webdnn.ts","../src/descriptor_runner/math.ts","../src/descriptor_runner/get_backend_availability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;EAsBE;AEtBF,gEAAgE;ACAhE,iEAAiE;ACAjE,IAAU,MAAM,CAwDf;AAxDD,WAAU,MAAM;IACZ;;OAEG;IACH;QAII,YAAY,UAAkB,EAAE,MAAc;YAC1C,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;YAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACzB,CAAC;KA4CJ;IAnDqB,aAAM,SAmD3B,CAAA;AACL,CAAC,EAxDS,MAAM,KAAN,MAAM,QAwDf;ACxDD,oCAAoC;AAEpC,IAAU,MAAM,CAiEf;AAjED,WAAU,MAAM;IACZ,kBAA0B,SAAQ,OAAA,MAAM;QAKpC,YAAY,UAAkB;YAC1B,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YAC5B,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,UAAU,GAAG,CAAC,CAAC,CAAA,8BAA8B;YACjD,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;YAClF,IAAI,CAAC,UAAU,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3D,CAAC;QAED,yEAAyE;QACzE,KAAK,CAAC,KAAK,CAAC,GAAoB,EAAE,UAAmB;YACjD,MAAM,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,YAAY,GAAG,IAAU,GAAG,CAAC,WAAY,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;YACtE,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QACtC,CAAC;QAED,KAAK,CAAC,IAAI,CAA4B,GAAM,EAAE,aAAqB,CAAC,EAAE,MAAe;YACjF,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBACP,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;YAC1C,CAAC;YACD,MAAM,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;YACxC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC;gBACxB,kBAAkB;gBAClB,MAAM,CAAC;YACX,CAAC;YAED,IAAI,eAAe,GAAQ,GAAG,CAAC,WAAW,CAAC,CAAA,mBAAmB;YAC9D,IAAI,YAAY,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,GAAG,UAAU,GAAG,eAAe,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;YAEpJ,EAAE,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC;gBACvB,MAAM,GAAG,YAAY,CAAC,MAAM,GAAG,UAAU,CAAC;YAC9C,CAAC;YACK,GAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7B,MAAM,CAAC;QACX,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,aAA4B;YACpC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACvC,CAAC;QAED,YAAY,CAAC,MAAc,EAAE,MAAc,EAAE,WAAgB;YACzD,IAAI,YAAY,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,GAAG,MAAM,GAAG,WAAW,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;YACxI,MAAM,CAAC,YAAY,CAAC;QACxB,CAAC;QAED,WAAW,CAAC,MAAc,EAAE,MAAc,EAAE,WAAgB;YACxD,IAAI,YAAY,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,GAAG,MAAM,GAAG,WAAW,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;YACxI,MAAM,CAAC,YAAY,CAAC;QACxB,CAAC;QAED,KAAK,CAAC,cAAc;YAChB,iBAAiB;QACrB,CAAC;QAED,KAAK,CAAC,aAAa;YACf,4FAA4F;YAC5F,MAAM,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;QAC5C,CAAC;KACJ;IA/DY,mBAAY,eA+DxB,CAAA;AACL,CAAC,EAjES,MAAM,KAAN,MAAM,QAiEf;ACnED,kDAAkD;AAElD,IAAU,MAAM,CAiGf;AAjGD,WAAU,MAAM;IACZ;QAMI,KAAK,CAAC,IAAI;YACN,gDAAgD;YAChD,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC;gBACpC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,CAAC,OAAO,GAAiC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAE,CAAC,CAAA,YAAY;YAChH,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;YACtD,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,EAAE,CAAC;QACpC,CAAC;QAED,YAAY,CAAC,WAA4B;YACrC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAClD,CAAC;QAED,UAAU,CAAC,aAAqB,EAAE,YAAoB,EAAE;YACpD,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;YAExD,GAAG,CAAC,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;gBACrC,IAAI,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;gBACpD,IAAI,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,cAAc,CAAC,CAAC;gBAE7E,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,GAAG,GAAG,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC;YACpE,CAAC;QACL,CAAC;QAED,mBAAmB;YACf,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,mBAAmB,EAAE,CAAC;QACnD,CAAC;QAED,sBAAsB,CAAC,IAAY;YAC/B,IAAI,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC1C,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;gBACT,MAAM,SAAS,CAAC,oBAAoB,IAAI,kBAAkB,CAAC,CAAC;YAChE,CAAC;YACD,MAAM,CAAC,KAAK,CAAC;QACjB,CAAC;QAED,0BAA0B,CAAC,IAAY,EACZ,mBAA+B,EAC/B,qBAAiC,EACjC,OAAwC,EACxC,mBAA6B;YACpD,IAAI,aAAa,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC/C,IAAI,cAAc,GAAG,aAAa,CAAC,2BAA2B,EAAE,CAAC;YAEjE,cAAc,CAAC,uBAAuB,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC1E,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,IAAI,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;gBACxB,IAAI,KAAmB,CAAC;gBACxB,EAAE,CAAC,CAAC,MAAM,YAAY,OAAA,YAAY,CAAC,CAAC,CAAC;oBACjC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;gBAC1B,CAAC;gBAAC,IAAI,CAAC,CAAC;oBACJ,4DAA4D;oBAC5D,KAAK,GAAG,MAAM,CAAC;gBACnB,CAAC;gBAED,cAAc,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1C,CAAC;YACD,cAAc,CAAC,QAAQ,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,CAAC;YACpE,cAAc,CAAC,WAAW,EAAE,CAAC;YAC7B,IAAI,OAAO,GAAyB,IAAI,CAAC;YACzC,EAAE,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBACtB,OAAO,GAAG,aAAa,CAAC,SAAS,CAAC;YACtC,CAAC;YACD,aAAa,CAAC,MAAM,EAAE,CAAC;YACvB,MAAM,CAAC,OAAO,CAAC;QACnB,CAAC;QAED,KAAK,CAAC,IAAI;YACN,IAAI,aAAa,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC/C,IAAI,cAAc,GAAG,aAAa,CAAC,2BAA2B,EAAE,CAAC;YAEjE,cAAc,CAAC,uBAAuB,CAAC,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC,CAAC,CAAC;YAClF,cAAc,CAAC,QAAQ,CAAC;gBACpB,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,KAAK,EAAE,CAAC;aACX,EAAE;gBACC,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,KAAK,EAAE,CAAC;aACX,CAAC,CAAC;YACH,cAAc,CAAC,WAAW,EAAE,CAAC;YAC7B,IAAI,OAAO,GAAG,aAAa,CAAC,SAAS,CAAC;YACtC,aAAa,CAAC,MAAM,EAAE,CAAC;YACvB,MAAM,CAAC,OAAO,CAAC;QACnB,CAAC;KACJ;IA7FY,oBAAa,gBA6FzB,CAAA;IAED,aAAa,CAAC,kBAAkB,GAAG,wBAAwB,IAAI,MAAM,IAAI,6BAA6B,IAAI,MAAM,CAAC;AACrH,CAAC,EAjGS,MAAM,KAAN,MAAM,QAiGf;ACnGD,2CAA2C;AAE3C,IAAU,MAAM,CAMf;AAND,WAAU,MAAM;IACZ;QACI,KAAK,CAAC,MAAM,CAAC,IAAgB,EAAE,iBAAmC;YAC9D,MAAM,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;QAC/E,CAAC;KACJ;IAJY,uBAAgB,mBAI5B,CAAA;AACL,CAAC,EANS,MAAM,KAAN,MAAM,QAMf;ACRD,+CAA+C;ACA/C,2CAA2C;AAI3C,IAAU,MAAM,CAsDf;AAtDD,WAAU,MAAM;IACZ;QAwBI,KAAK,CAAC,MAAM,CAAC,IAAgB,EAAE,iBAAmC;YAC9D,IAAI,GAAG,GAAG,IAAI,YAAY,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;YACzD,IAAI,SAAS,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3D,IAAI,UAAU,GAAG,CAAC,CAAC;YACnB,OAAO,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC9B,IAAI,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBACtD,UAAU,IAAI,CAAC,CAAC;gBAChB,IAAI,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBACrD,UAAU,IAAI,CAAC,CAAC;gBAChB,IAAI,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBACnD,UAAU,IAAI,CAAC,CAAC;gBAChB,IAAI,YAAY,GAAG,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;gBACzC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC3B,YAAY,CAAC,CAAC,CAAC,GAAG,qBAAqB,CAAC,YAAY,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBACpG,CAAC;gBAED,YAAY;gBACZ,IAAI,aAAa,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC;gBACzF,IAAI,OAAO,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;gBAC9C,IAAI,YAAY,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;gBACxC,IAAI,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC;gBACnC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;oBAChC,GAAG,CAAC,UAAU,EAAE,CAAC,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;gBACtD,CAAC;gBACD,UAAU,IAAI,SAAS,CAAC;YAC5B,CAAC;YACD,MAAM,CAAC,GAAG,CAAC;QACf,CAAC;;IAlDM,kCAAY,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QAC5H,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe;QACpG,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,GAAG;KAC5G,CAAC;IAtBO,4BAAqB,wBAoDjC,CAAA;AACL,CAAC,EAtDS,MAAM,KAAN,MAAM,QAsDf;AC1DD,2CAA2C;AAC3C,+CAA+C;AAC/C,oDAAoD;AAEpD,IAAU,MAAM,CAWf;AAXD,WAAU,MAAM;IACZ,4BAAmC,IAAY;QAC3C,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YACX,KAAK,KAAK;gBACN,MAAM,CAAC,IAAI,OAAA,gBAAgB,EAAE,CAAC;YAClC,KAAK,UAAU;gBACX,MAAM,CAAC,IAAI,OAAA,qBAAqB,EAAE,CAAC;YACvC;gBACI,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACnD,CAAC;IACL,CAAC;IATe,yBAAkB,qBASjC,CAAA;AACL,CAAC,EAXS,MAAM,KAAN,MAAM,QAWf;ACfD,IAAU,MAAM,CA4Cf;AA5CD,WAAU,MAAM;IACZ,IAAiB,IAAI,CA0CpB;IA1CD,WAAiB,IAAI;QACjB,MAAM,aAAa,GAAG,CAAC,CAAC,CAAC;QAEzB;;WAEG;QACH;YAAA;gBACY,wBAAmB,GAAW,aAAa,CAAC;YAkCxD,CAAC;YA/BG;;;;eAIG;YACH,OAAO,CAAC,EAAa;gBACjB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;gBACb,EAAE,CAAC,CAAC,IAAI,CAAC,mBAAmB,IAAI,aAAa,CAAC,CAAC,CAAC;oBAC5C,IAAI,CAAC,mBAAmB,GAAG,qBAAqB,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;gBACjF,CAAC;YACL,CAAC;YAED;;eAEG;YACH,aAAa;gBACT,EAAE,CAAC,CAAC,IAAI,CAAC,mBAAmB,IAAI,aAAa,CAAC;oBAAC,MAAM,CAAC;gBAEtD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,IAAI,CAAC,EAAE,EAAE,CAAC;YACd,CAAC;YAED;;eAEG;YACH,MAAM;gBACF,EAAE,CAAC,CAAC,IAAI,CAAC,mBAAmB,IAAI,aAAa,CAAC;oBAAC,MAAM,CAAC;gBAEtD,oBAAoB,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;gBAC/C,IAAI,CAAC,mBAAmB,GAAG,aAAa,CAAC;YAC7C,CAAC;SACJ;QAnCY,sBAAiB,oBAmC7B,CAAA;IACL,CAAC,EA1CgB,IAAI,GAAJ,WAAI,KAAJ,WAAI,QA0CpB;AACL,CAAC,EA5CS,MAAM,KAAN,MAAM,QA4Cf;AC5CD,qDAAqD;AAErD,IAAI,iBAAiB,GAA6B,GAAG,IAAI,GAAG,CAAC;AAE7D;;;;;GAKG;AACH,IAAI,aAAa,GAAkE,MAAM,CAAC,KAAK,CAAC;AAEhG,IAAU,MAAM,CAuEf;AAvED,WAAU,MAAM;IACZ;;;OAGG;IACH,sBAA6B,GAAW;QACpC,MAAM,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAFe,mBAAY,eAE3B,CAAA;IAED;;;OAGG;IACH,mCAA0C,QAAkC;QACxE,iBAAiB,GAAG,QAAQ,CAAC;IACjC,CAAC;IAFe,gCAAyB,4BAExC,CAAA;IAED;;;OAGG;IACH,+BAAsC,QAAuE;QACzG,aAAa,GAAG,QAAQ,CAAC;IAC7B,CAAC;IAFe,4BAAqB,wBAEpC,CAAA;IAED;;;;;OAKG;IACH,eAAsB,KAAkB,EAAE,IAAkB;QACxD,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAFe,YAAK,QAEpB,CAAA;IAED;;;;;OAKG;IACH,sCAA6C,GAAa,EAAE,QAAiD;QACzG,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QAErD,IAAI,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACtD,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;YAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;QAEtC,IAAI,MAAM,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QAClC,IAAI,iBAAiB,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAE5D,8BAA8B,KAAK;YAC/B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;YAE7B,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACX,iBAAiB,CAAC,OAAO,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;YAC7D,CAAC;YAED,EAAE,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC;gBAClB,iBAAiB,CAAC,aAAa,EAAE,CAAC;gBAClC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YACzB,CAAC;YAAC,IAAI,CAAC,CAAC;gBACJ,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACpD,CAAC;QACL,CAAC;QAED,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACpD,CAAC;IA7Be,mCAA4B,+BA6B3C,CAAA;AACL,CAAC,EAvES,MAAM,KAAN,MAAM,QAuEf;ACnFD,8CAA8C;ACA9C,mDAAmD;AACnD,6CAA6C;AAC7C,+CAA+C;AAC/C,yDAAyD;AACzD,oCAAoC;AACpC,uEAAuE;AAEvE,IAAU,MAAM,CAuJf;AAvJD,WAAU,MAAM;IACZ;QAUI,YAAoB,aAA4B;YAA5B,kBAAa,GAAb,aAAa,CAAe;YALzC,gBAAW,GAAY,KAAK,CAAC;YAC7B,YAAO,GAAW,QAAQ,CAAC;QAMlC,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,SAAiB,EAAE,gBAAyD;YACnF,IAAI,SAAS,GAAG,GAAG,SAAS,UAAU,IAAI,CAAC,OAAO,OAAO,CAAC;YAC1D,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,SAAS,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACpC,CAAC;YACD,SAAS,GAAG,OAAA,YAAY,CAAC,SAAS,CAAC,CAAC;YACpC,IAAI,WAAW,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAChD,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,mBAAmB,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;YAC3C,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAErB,IAAI,UAAU,GAAG,GAAG,SAAS,WAAW,IAAI,CAAC,OAAO,MAAM,CAAC;YAC3D,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,UAAU,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACrC,CAAC;YACD,UAAU,GAAG,OAAA,YAAY,CAAC,UAAU,CAAC,CAAC;YACtC,IAAI,eAAe,GAAG,MAAM,OAAA,4BAA4B,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,CAAC;YAC7H,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,aAAa,CAAC,UAAiC;YAC3C,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QACjC,CAAC;QAED,KAAK,CAAC,OAAO;YACT,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;YAC3E,IAAI,CAAC,SAAS,GAAG,IAAI,OAAA,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,UAAU,GAAG,YAAY,CAAC,iBAAiB,CAAC,CAAC;YACjH,IAAI,CAAC,OAAO,GAAG,IAAI,OAAA,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,UAAU,GAAG,YAAY,CAAC,iBAAiB,CAAC,CAAC;YACjH,IAAI,CAAC,oBAAoB,GAAG,EAAE,CAAC;YAC/B,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;gBAC9C,IAAI,GAAG,GAAG,IAAI,OAAA,YAAY,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,GAAG,YAAY,CAAC,iBAAiB,CAAC,CAAC;gBAC1F,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;gBACvD,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACxC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,WAAuB;YACrC,IAAI,OAAO,GAAG,OAAA,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;YAClE,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC;QACrG,CAAC;QAED,KAAK,CAAC,aAAa;YACf,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;gBAClB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAC3B,CAAC;YACD,IAAI,KAAK,GAAmB,EAAE,CAAC;YAC/B,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACrD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC1F,KAAK,CAAC,IAAI,CAAe,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC;YACxG,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YACxB,MAAM,CAAC,KAAK,CAAC;QACjB,CAAC;QAED,KAAK,CAAC,cAAc;YAChB,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;YAC5B,CAAC;YACD,IAAI,KAAK,GAAmB,EAAE,CAAC;YAC/B,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC3F,KAAK,CAAC,IAAI,CAAe,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC;YACvG,CAAC;YACD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;YACzB,MAAM,CAAC,KAAK,CAAC;QACjB,CAAC;QAED,KAAK,CAAC,GAAG;YACL,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACxC,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;YACpF,CAAC;YAED,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gBACpB,IAAI,OAAO,GAAQ,EAAE,CAAC;gBACtB,IAAI,gBAAgB,GAAG,CAAC,CAAC;gBAEzB,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACzD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;oBAE9C,IAAI,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;oBAC9B,MAAM,IAAI,CAAC,aAAa,CAAC,0BAA0B,CAC/C,aAAa,GAAG,SAAS,CAAC,eAAe,EACzC,SAAS,CAAC,qBAAqB,EAC/B,SAAS,CAAC,wBAAwB,EAClC,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAC5D,IAAI,CACP,CAAC;oBACF,IAAI,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;oBAC5C,OAAO,CAAC,IAAI,CAAC;wBACT,QAAQ,EAAE,SAAS,CAAC,eAAe;wBACnC,mBAAmB,EAAE,WAAW;qBACnC,CAAC,CAAC;oBACH,gBAAgB,IAAI,WAAW,CAAC;gBACpC,CAAC;gBAED,IAAI,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,MAAM;oBAClE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;wBACjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG;4BACxB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;4BAC1B,OAAO,EAAE,CAAC;4BACV,mBAAmB,EAAE,CAAC;yBACzB,CAAC;oBACN,CAAC;oBAED,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;oBACrC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,MAAM,CAAC,mBAAmB,CAAC,CAAC;oBAE9E,MAAM,CAAC,OAAO,CAAC;gBACnB,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;gBAET,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC,GAAG,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE7G,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACvB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAE3B,CAAC;YAAC,IAAI,CAAC,CAAC;gBACJ,IAAI,gBAAgB,GAAyB,IAAI,CAAC;gBAClD,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACzD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;oBAC9C,IAAI,OAAO,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;oBACzD,gBAAgB,GAAG,IAAI,CAAC,aAAa,CAAC,0BAA0B,CAC5D,aAAa,GAAG,SAAS,CAAC,eAAe,EACzC,SAAS,CAAC,qBAAqB,EAC/B,SAAS,CAAC,wBAAwB,EAClC,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAC5D,OAAO,CACV,CAAC;gBACN,CAAC;gBAED,MAAM,gBAAiB,CAAC,CAAA,6BAA6B;YACzD,CAAC;QACL,CAAC;KACJ;IArJY,6BAAsB,yBAqJlC,CAAA;AACL,CAAC,EAvJS,MAAM,KAAN,MAAM,QAuJf;AC9JD,yEAAyE;AAIzE,IAAU,MAAM,CA4Bf;AA5BD,WAAU,MAAM;IACZ;QAII,YAAoB,MAAY;YAAZ,WAAM,GAAN,MAAM,CAAM;YAC5B,EAAE,CAAC,CAAC,CAAC,OAAA,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC;gBACpC,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAC/D,CAAC;QACL,CAAC;QAED,KAAK,CAAC,IAAI;YACN,mCAAmC;YACnC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;YAC9B,IAAI,CAAC,aAAa,GAAG,IAAI,OAAA,aAAa,EAAE,CAAC;YACzC,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;YAChC,OAAA,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACtC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC9B,CAAC;QAEO,kBAAkB;YACtB,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;QACnE,CAAC;QAED,sBAAsB;YAClB,MAAM,CAAC,IAAI,OAAA,sBAAsB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1D,CAAC;KACJ;IA1BY,yBAAkB,qBA0B9B,CAAA;AACL,CAAC,EA5BS,MAAM,KAAN,MAAM,QA4Bf;AChCD,8CAA8C;ACA9C,6CAA6C;AAC7C,+CAA+C;AAC/C,yDAAyD;AACzD,oCAAoC;AACpC,4EAA4E;AAE5E,IAAU,MAAM,CAoKf;AApKD,WAAU,MAAM;IACZ;QAWI;YANO,gBAAW,GAAY,KAAK,CAAC;YAC7B,YAAO,GAAW,aAAa,CAAC;YAE/B,+BAA0B,GAAQ,IAAI,CAAC;YACvC,yBAAoB,GAAQ,IAAI,CAAC;QAIzC,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,SAAiB,EAAE,gBAAyD;YACnF,IAAI,SAAS,GAAG,GAAG,SAAS,UAAU,IAAI,CAAC,OAAO,OAAO,CAAC;YAC1D,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,SAAS,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACpC,CAAC;YACD,SAAS,GAAG,OAAA,YAAY,CAAC,SAAS,CAAC,CAAC;YACpC,IAAI,WAAW,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAChD,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,mBAAmB,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;YAE3C,4DAA4D;YAC5D,IAAI,cAAc,GAAG,OAAO,WAAW,KAAK,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;YAC/E,IAAI,oBAAoB,GAAG,GAAG,SAAS,YAAY,cAAc,KAAK,CAAC;YACvE,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,oBAAoB,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC/C,CAAC;YACD,oBAAoB,GAAG,OAAA,YAAY,CAAC,oBAAoB,CAAC,CAAC;YAC1D,IAAI,CAAC,oBAAoB,GAAG,oBAAoB,CAAC;YAEjD,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAErB,IAAI,UAAU,GAAG,GAAG,SAAS,WAAW,IAAI,CAAC,OAAO,MAAM,CAAC;YAC3D,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,UAAU,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACrC,CAAC;YACD,UAAU,GAAG,OAAA,YAAY,CAAC,UAAU,CAAC,CAAC;YACtC,IAAI,eAAe,GAAG,MAAM,OAAA,4BAA4B,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;YAC3G,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,aAAa,CAAC,UAAsC;YAChD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QACjC,CAAC;QAED,OAAO;YACH,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACpD,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK;gBACxB,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;gBACpD,EAAE,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;oBAClC,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC;gBAC3C,CAAC;gBAAC,IAAI,CAAC,CAAC;oBACJ,IAAI,CAAC,oBAAoB,GAAG,KAAK,CAAC;gBACtC,CAAC;YACL,CAAC,CAAC;YACF,IAAI,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM;gBAC5C,EAAE,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC;oBAC5B,+CAA+C;oBAC/C,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;oBAClC,MAAM,CAAC;gBACX,CAAC;gBACD,IAAI,CAAC,0BAA0B,GAAG,MAAM,CAAC;gBACzC,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,KAAK;oBAC1B,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;wBACnB,OAAO,EAAE,CAAC;oBACd,CAAC;oBAAC,IAAI,CAAC,CAAC;wBACJ,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;wBACxB,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;oBAClC,CAAC;gBACL,CAAC,CAAC;gBACF,4CAA4C;YAChD,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,OAAO,CAAC;QACnB,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,WAAuB;YACrC,IAAI,OAAO,GAAG,OAAA,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;YAClE,IAAI,WAAW,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACvF,IAAI,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM;gBAC5C,IAAI,CAAC,0BAA0B,GAAG,MAAM,CAAC;gBACzC,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,KAAK;oBAC1B,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;wBACnB,OAAO,EAAE,CAAC;oBACd,CAAC;oBAAC,IAAI,CAAC,CAAC;wBACJ,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;wBACxB,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;oBAClC,CAAC;gBACL,CAAC,CAAC;gBAEF,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAC,CAAC,CAAC;YACjE,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,OAAO,CAAC;QACnB,CAAC;QAED,KAAK,CAAC,aAAa;YACf,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;gBAClB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAC3B,CAAC;YACD,IAAI,KAAK,GAAmB,EAAE,CAAC;YAC/B,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACrD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC1F,KAAK,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;YACjD,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YACxB,MAAM,CAAC,KAAK,CAAC;QACjB,CAAC;QAED,KAAK,CAAC,cAAc;YAChB,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;YAC5B,CAAC;YACD,IAAI,KAAK,GAAmB,EAAE,CAAC;YAC/B,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC3F,KAAK,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;YACjD,CAAC;YACD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;YACzB,MAAM,CAAC,KAAK,CAAC;QACjB,CAAC;QAED,KAAK,CAAC,GAAG;YACL,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACxC,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;YACpF,CAAC;YACD,IAAI,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM;gBAC5C,yDAAyD;gBACzD,IAAI,CAAC,0BAA0B,GAAG,MAAM,CAAC;gBACzC,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,KAAK;oBAC1B,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;wBAC5B,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;4BACzC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;wBAC3C,CAAC;wBACD,OAAO,EAAE,CAAC;oBACd,CAAC;oBAAC,IAAI,CAAC,CAAC;wBACJ,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;wBACxB,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;oBAClC,CAAC;gBACL,CAAC,CAAC;gBAEF,IAAI,MAAM,GAAQ,EAAE,CAAC;gBACrB,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACrD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC1F,MAAM,CAAC,IAAI,CAAC,EAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAC,CAAC,CAAC;gBAC5F,CAAC;gBACD,IAAI,OAAO,GAAQ,EAAE,CAAC;gBACtB,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACtD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC3F,OAAO,CAAC,IAAI,CAAC,EAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAC,CAAC,CAAC;gBACnE,CAAC;gBACD,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAC,CAAC,CAAC;YAC7E,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,OAAO,CAAC;QACnB,CAAC;KACJ;IAlKY,kCAA2B,8BAkKvC,CAAA;AACL,CAAC,EApKS,MAAM,KAAN,MAAM,QAoKf;AC1KD,2CAA2C;AAC3C,8EAA8E;AAI9E,IAAU,MAAM,CAoBf;AApBD,WAAU,MAAM;IACZ;QAEI,YAAoB,MAAY;YAAZ,WAAM,GAAN,MAAM,CAAM;YAC5B,EAAE,CAAC,CAAC,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC;gBAChC,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;YACnE,CAAC;YACD,EAAE,CAAC,CAAC,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC;gBAClC,OAAO,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;YAC5F,CAAC;QACL,CAAC;QAED,KAAK,CAAC,IAAI;QACV,CAAC;QAGD,sBAAsB;YAClB,MAAM,CAAC,IAAI,OAAA,2BAA2B,EAAE,CAAC;QAC7C,CAAC;KACJ;IAlBY,8BAAuB,0BAkBnC,CAAA;AACL,CAAC,EApBS,MAAM,KAAN,MAAM,QAoBf;ACzBD,8CAA8C;ACA9C,mCAAmC;AACnC,wEAAwE;AAExE,IAAU,MAAM,CAuHf;AAvHD,WAAU,MAAM;IACZ;QAWI;YALO,gBAAW,GAAY,KAAK,CAAC;YAC7B,YAAO,GAAW,UAAU,CAAC;QAKpC,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,SAAiB,EAAE,gBAAyD;YACnF,IAAI,SAAS,GAAG,GAAG,SAAS,UAAU,IAAI,CAAC,OAAO,OAAO,CAAC;YAC1D,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,SAAS,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACpC,CAAC;YACD,SAAS,GAAG,OAAA,YAAY,CAAC,SAAS,CAAC,CAAC;YACpC,IAAI,WAAW,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAChD,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,mBAAmB,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;YAC3C,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAErB,IAAI,UAAU,GAAG,GAAG,SAAS,WAAW,IAAI,CAAC,OAAO,MAAM,CAAC;YAC3D,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,UAAU,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACrC,CAAC;YACD,UAAU,GAAG,OAAA,YAAY,CAAC,UAAU,CAAC,CAAC;YACtC,IAAI,eAAe,GAAG,MAAM,OAAA,4BAA4B,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;YAC3G,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,aAAa,CAAC,UAAmC;YAC7C,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QACjC,CAAC;QAED,KAAK,CAAC,OAAO;YACT,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,IAAI,CAAC,cAAc,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;YACrF,IAAI,iBAAiB,GAAG,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,UAAU,CAAC;YACrE,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,EAAE,CAAC;YAC9B,GAAG,CAAC,CAAC,IAAI,IAAI,IAAI,iBAAiB,CAAC,CAAC,CAAC;gBACjC,IAAI,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;gBACpC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,iBAAiB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACzI,CAAC;YAED,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,EAAE,CAAC;YAChC,IAAI,mBAAmB,GAAG,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,UAAU,CAAC;YACzE,GAAG,CAAC,CAAC,IAAI,IAAI,IAAI,mBAAmB,CAAC,CAAC,CAAC;gBACnC,IAAI,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBACtC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAChE,CAAC;QACL,CAAC;QAEO,aAAa;YACjB,IAAI,mBAAwB,CAAC;YAC7B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;YACpC,IAAI,CAAC,SAAS,GAAG,mBAAmB,CAAC;QACzC,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,WAAuB;YACrC,0GAA0G;YAC1G,IAAI,OAAO,GAAG,OAAA,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;YAClE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAClG,CAAC;QAED,KAAK,CAAC,GAAG;YACL,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACxC,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;YACpF,CAAC;YACD,IAAI,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAChC,IAAI,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA,iBAAiB;YACrD,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzD,IAAI,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC9B,EAAE,CAAC,CAAC,YAAY,GAAG,kBAAkB,IAAI,IAAI,CAAC,CAAC,CAAC;oBAC5C,IAAI,UAAU,GAAG,YAAY,GAAG,cAAc,CAAC;oBAC/C,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,eAAe,UAAU,KAAK,CAAC,CAAC;oBAC/F,kBAAkB,GAAG,YAAY,CAAC;oBAClC,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;gBACjC,CAAC;gBACD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;gBAC9C,IAAI,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;gBACjF,IAAI,aAAa,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;gBACnF,IAAI,aAAa,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;gBACjF,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;YACjH,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,eAAe,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,KAAK,CAAC,CAAC;QACpJ,CAAC;QAED,KAAK,CAAC,eAAe;YACjB,sDAAsD;YACtD,MAAM,CAAC,IAAI,OAAO,CAAC,UAAU,OAAO,EAAE,MAAM;gBACxC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAC5B,CAAC,CAAC,CAAC;QACP,CAAC;QAED,KAAK,CAAC,aAAa;YACf,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;gBAClB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAC3B,CAAC;YACD,IAAI,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,CAAC;YACjF,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YACxB,MAAM,CAAC,KAAK,CAAC;QACjB,CAAC;QAED,KAAK,CAAC,cAAc;YAChB,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBACnB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;YAC5B,CAAC;YACD,IAAI,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,CAAC;YAClF,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;YACzB,MAAM,CAAC,KAAK,CAAC;QACjB,CAAC;KACJ;IArHY,+BAAwB,2BAqHpC,CAAA;AACL,CAAC,EAvHS,MAAM,KAAN,MAAM,QAuHf;AC1HD,2EAA2E;AAE3E,IAAU,MAAM,CAaf;AAbD,WAAU,MAAM;IACZ;QACI,YAAoB,MAAY;YAAZ,WAAM,GAAN,MAAM,CAAM;QAEhC,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,MAAY;QACvB,CAAC;QAED,sBAAsB;YAClB,MAAM,CAAC,IAAI,OAAA,wBAAwB,EAAE,CAAC;QAC1C,CAAC;KACJ;IAXY,2BAAoB,uBAWhC,CAAA;AACL,CAAC,EAbS,MAAM,KAAN,MAAM,QAaf;ACfD,wDAAwD;AACxD,+DAA+D;AAC/D,oEAAoE;AACpE,iEAAiE;AAEjE,IAAU,MAAM,CAuHf;AAvHD,WAAU,MAAM;IAGZ,IAAI,mBAA2C,CAAC;IAChD,IAAI,kBAA4B,CAAC;IACjC,IAAI,iBAAyB,CAAC;IAE9B,KAAK;QACD,IAAI,YAAY,GAAG,kBAAkB,CAAC,KAAK,EAAE,CAAC;QAC9C,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,MAAM,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,KAAmB,CAAC;QACxB,IAAI,CAAC;YACD,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;gBACnB,KAAK,QAAQ;oBACT,KAAK,GAAG,IAAI,OAAA,kBAAkB,CAAC,MAAM,CAAC,CAAC;oBACvC,KAAK,CAAC;gBACV,KAAK,aAAa;oBACd,KAAK,GAAG,IAAI,OAAA,uBAAuB,CAAC,MAAM,CAAC,CAAC;oBAC5C,KAAK,CAAC;gBACV,KAAK,UAAU;oBACX,KAAK,GAAG,IAAI,OAAA,oBAAoB,CAAC,MAAM,CAAC,CAAC;oBACzC,KAAK,CAAC;gBACV;oBACI,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,YAAY,CAAC,CAAC;YAC3D,CAAC;YACD,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,OAAA,GAAG,GAAG,KAAK,CAAC;YACZ,iBAAiB,GAAG,YAAY,CAAC;QACrC,CAAC;QAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACV,OAAO,CAAC,IAAI,CAAC,wBAAwB,YAAY,aAAa,EAAE,EAAE,CAAC,CAAC;YACpE,MAAM,CAAC,MAAM,WAAW,EAAE,CAAC;QAC/B,CAAC;QAED,MAAM,CAAC,iBAAiB,CAAC;IAC7B,CAAC;IAEM,KAAK,eAAe,YAAgC,EAAE,iBAAyC,EAAE;QACpG,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;YAChB,YAAY,GAAG,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QAC7C,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC;YAC1C,YAAY,GAAG,CAAC,YAAY,CAAC,CAAC;QAClC,CAAC;QAED,mBAAmB,GAAG,cAAc,CAAC;QACrC,kBAAkB,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QAEvD,MAAM,WAAW,EAAE,CAAC;QAEpB,MAAM,CAAC,iBAAiB,CAAC;IAC7B,CAAC;IAbqB,WAAI,OAazB,CAAA;IAcD;;;;;OAKG;IACI,KAAK,qBAAqB,SAAiB,EAAE,aAAyB,EAAE;QAC3E,MAAM,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC;QAE/D,OAAO,IAAI,EAAE,CAAC;YACV,IAAI,CAAC;gBACD,IAAI,MAAM,GAAG,OAAA,GAAG,CAAC,sBAAsB,EAAE,CAAC;gBAC1C,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAC;gBAE1D,IAAI,UAAU,GAAG,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC;gBAC9C,IAAI,WAAW,GAAG,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;gBAEhD,MAAM,CAAC;oBACH,WAAW,EAAE,iBAAiB;oBAC9B,UAAU,EAAE,UAAU;oBACtB,WAAW,EAAE,WAAW;oBACxB,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;iBAC/B,CAAC;YAEN,CAAC;YAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACV,OAAO,CAAC,KAAK,CAAC,4BAA4B,iBAAiB,kCAAkC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC3G,MAAM,WAAW,EAAE,CAAC;YACxB,CAAC;QACL,CAAC;IACL,CAAC;IAvBqB,iBAAU,aAuB/B,CAAA;AAuBL,CAAC,EAvHS,MAAM,KAAN,MAAM,QAuHf;AC5HD,IAAU,MAAM,CA8Gf;AA9GD,WAAU,MAAM;IACZ,IAAiB,IAAI,CA4GpB;IA5GD,WAAiB,IAAI;QACjB;;;;;WAKG;QACH,gBAAuB,GAAa,EAAE,IAAY,CAAC;YAC/C,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9B,IAAI,SAAS,GAA8B,EAAE,CAAC;YAE9C,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,MAAM,GAAG,KAAK,CAAC,KAAK,EAAG,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,EACzD,KAAK,GAAG,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,EACnB,IAAI,GAAG,IAAI,EACX,KAAK,GAAG,EAAE,GAAG,CAAC,EACd,GAAG,CAAC;gBAER,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;oBAAC,QAAQ,CAAC;gBAEzB,OAAO,IAAI,EAAE,CAAC;oBACV,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,IAAI,IAAI,KAAK;wBAAE,IAAI,EAAE,CAAC;oBAClD,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK;wBAAE,KAAK,EAAE,CAAC;oBAErD,EAAE,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC;wBAAC,KAAK,CAAC;oBACzB,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;oBACxB,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;oBAChC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;oBAEjB,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;oBAC9B,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;oBAC5C,SAAS,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;gBAC3B,CAAC;gBAED,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;oBACjB,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;oBACxB,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;oBAElB,GAAG,GAAG,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;oBAClC,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;oBAC5C,SAAS,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;gBAC1B,CAAC;gBAED,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;gBAC5B,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;oBAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YACpD,CAAC;YAED,IAAI,MAAM,GAAa,EAAE,CAAC;YAC1B,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzB,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACnD,CAAC;YAED,MAAM,CAAC,MAAM,CAAC;QAClB,CAAC;QA9Ce,WAAM,SA8CrB,CAAA;QAED;;;;;WAKG;QACH,gBAAuB,GAAa,EAAE,IAAY,CAAC;YAC/C,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9B,IAAI,SAAS,GAA8B,EAAE,CAAC;YAE9C,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,MAAM,GAAG,KAAK,CAAC,KAAK,EAAG,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,EACzD,KAAK,GAAG,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,EACnB,IAAI,GAAG,IAAI,EACX,KAAK,GAAG,EAAE,GAAG,CAAC,EACd,GAAG,CAAC;gBAER,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;oBAAC,QAAQ,CAAC;gBAEzB,OAAO,IAAI,EAAE,CAAC;oBACV,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,IAAI,IAAI,KAAK;wBAAE,IAAI,EAAE,CAAC;oBAClD,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK;wBAAE,KAAK,EAAE,CAAC;oBAErD,EAAE,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC;wBAAC,KAAK,CAAC;oBACzB,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;oBACxB,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;oBAChC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;oBAEjB,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;oBAC9B,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;oBAC5C,SAAS,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;gBAC3B,CAAC;gBAED,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;oBACjB,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;oBACxB,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;oBAElB,GAAG,GAAG,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;oBAClC,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;oBAC5C,SAAS,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;gBAC1B,CAAC;gBAED,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;gBAC5B,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;oBAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YACpD,CAAC;YAED,IAAI,MAAM,GAAa,EAAE,CAAC;YAC1B,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzB,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACnD,CAAC;YAED,MAAM,CAAC,MAAM,CAAC;QAClB,CAAC;QA9Ce,WAAM,SA8CrB,CAAA;IACL,CAAC,EA5GgB,IAAI,GAAJ,WAAI,KAAJ,WAAI,QA4GpB;AACL,CAAC,EA9GS,MAAM,KAAN,MAAM,QA8Gf;AC9GD,IAAU,MAAM,CA+Bf;AA/BD,WAAU,MAAM;IACZ;QACI,MAAM,CAAC,6BAA6B,IAAI,MAAM,CAAC;IACnD,CAAC;IAED;QACI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC;IAC9B,CAAC;IAED;QACI,MAAM,CAAC,IAAI,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH;QACI,IAAI,MAAM,GAAgC;YACtC,QAAQ,EAAE,4BAA4B,EAAE;YACxC,aAAa,EAAE,iCAAiC,EAAE;YAClD,UAAU,EAAE,8BAA8B,EAAE;SAC/C,CAAC;QAEF,IAAI,KAAK,GAAG,CAAC,QAAQ,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAErF,MAAM,CAAC;YACH,MAAM,EAAE,MAAM;YACd,YAAY,EAAE,KAAK;SACtB,CAAA;IACL,CAAC;IAbe,6BAAsB,yBAarC,CAAA;AACL,CAAC,EA/BS,MAAM,KAAN,MAAM,QA+Bf"} \ No newline at end of file +{"version":3,"file":"webdnn.js","sourceRoot":"","sources":["../src/descriptor_runner/license.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor.ts","../src/descriptor_runner/placeholder.ts","../src/descriptor_runner/memory_layout.ts","../src/descriptor_runner/symbolic_array_buffer_view.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner.ts","../src/descriptor_runner/buffer/buffer.ts","../src/descriptor_runner/buffer/buffer_webgpu.ts","../src/descriptor_runner/webgpu_handler.ts","../src/descriptor_runner/decoder/weight_decoder.ts","../src/descriptor_runner/decoder/weight_decoder_raw.ts","../src/descriptor_runner/decoder/weight_decoder_eightbit.ts","../src/descriptor_runner/decoder/get_weight_decoder.ts","../src/descriptor_runner/util/dispatch_scheduler.ts","../src/descriptor_runner/fetch.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_webgpu.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_webgpu.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_webassembly.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_webassembly.ts","../src/descriptor_runner/graph_descriptor/graph_descriptor_fallback.ts","../src/descriptor_runner/descriptor_runner/descriptor_runner_fallback.ts","../src/descriptor_runner/webdnn.ts","../src/descriptor_runner/math.ts","../src/descriptor_runner/get_backend_availability.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/docs/source/api_reference/graph_transpiler/base_classes.md b/docs/source/api_reference/graph_transpiler/base_classes.md index 819f87ea6..a240c9bd7 100644 --- a/docs/source/api_reference/graph_transpiler/base_classes.md +++ b/docs/source/api_reference/graph_transpiler/base_classes.md @@ -44,6 +44,13 @@ :members: ``` +### Placeholder + +```eval_rst +.. autoclass:: webdnn.graph.placeholder.Placeholder + :members: +``` + ### OptimizeRule ```eval_rst diff --git a/docs/source/api_reference/graph_transpiler/index.md b/docs/source/api_reference/graph_transpiler/index.md index 5cf39bfa7..f0ce15c8d 100644 --- a/docs/source/api_reference/graph_transpiler/index.md +++ b/docs/source/api_reference/graph_transpiler/index.md @@ -1,4 +1,4 @@ -# Graph Builder +# Graph Transpiler ```eval_rst .. toctree:: diff --git a/docs/source/tutorial/caffe.md b/docs/source/tutorial/caffe.md index a689ade98..c4b1cbae2 100644 --- a/docs/source/tutorial/caffe.md +++ b/docs/source/tutorial/caffe.md @@ -16,7 +16,7 @@ First, You have to initialize [`DescriptorRunner`](../api_reference/descriptor_r and load model data. ```js -let runner = await WebDNN.prepareAll('./output'); +let runner = await WebDNN.load('./output'); ``` WebDNN automatically select the best backend based on Browser type and @@ -30,11 +30,11 @@ console.log(runner.backendName); ![backend](../_static/tutorial/check_backend.png) -Then you can get input and output variable references. +Then you can get input and output variable references (`SymbolicFloat32Array` type). ```js -let x = runner.inputViews[0]; -let y = runner.outputViews[0]; +let x = runner.getInputViews()[0]; +let y = runner.getOutputViews()[0]; ``` That's all for initialization. You only have to do this at once in the application. @@ -61,8 +61,9 @@ That's all. Show computed vector and predicted label. ```js -console.log('Computed vector', y); -console.log('Predicted Label', WebDNN.Math.argmax(y)); +let y_typed_array = y.toActual(); +console.log('Computed vector', y_typed_array); +console.log('Predicted Label', WebDNN.Math.argmax(y_typed_array)); ``` ![result](../_static/tutorial/result.png) diff --git a/docs/source/tutorial/chainer.md b/docs/source/tutorial/chainer.md index 9c1103ee3..b73aa0a51 100644 --- a/docs/source/tutorial/chainer.md +++ b/docs/source/tutorial/chainer.md @@ -26,7 +26,7 @@ pretrained in Chainer [#f2]_ into WebDNN execution format. 3. Convert chainer computation graph to our computation graph format ```python - from webdnn.graph.converters.chainer import ChainerGraphConverter + from webdnn.frontend.chainer import ChainerGraphConverter graph = ChainerGraphConverter().convert_from_inout_vars([x], [y]) ``` diff --git a/docs/source/tutorial/index.md b/docs/source/tutorial/index.md index 40211b5b1..4085cd9e3 100644 --- a/docs/source/tutorial/index.md +++ b/docs/source/tutorial/index.md @@ -6,6 +6,7 @@ ./introduction ./setup + ./setup_windows ./keras ./caffe ./chainer diff --git a/docs/source/tutorial/keras.md b/docs/source/tutorial/keras.md index 5a03f26fa..3fcfc26e9 100644 --- a/docs/source/tutorial/keras.md +++ b/docs/source/tutorial/keras.md @@ -32,7 +32,7 @@ pretrained in Keras [#f2]_ into WebDNN execution format. and load model data. ```js - let runner = await WebDNN.prepareAll('./output'); + let runner = await WebDNN.load('./output'); ``` WebDNN automatically select the best backend based on Browser type and @@ -46,11 +46,11 @@ pretrained in Keras [#f2]_ into WebDNN execution format. ![backend](../_static/tutorial/check_backend.png) - Then you can get input and output variable references. + Then you can get input and output variable references (`SymbolicFloat32Array` type). ```js - let x = runner.inputViews[0]; - let y = runner.outputViews[0]; + let x = runner.getInputViews()[0]; + let y = runner.getOutputViews()[0]; ``` That's all for initialization. You only have to do this at once in the application. @@ -77,8 +77,9 @@ pretrained in Keras [#f2]_ into WebDNN execution format. Show computed vector and predicted label. ```js - console.log('Computed vector', y); - console.log('Predicted Label', WebDNN.Math.argmax(y)); + let y_typed_array = y.toActual(); + console.log('Computed vector', y_typed_array); + console.log('Predicted Label', WebDNN.Math.argmax(y_typed_array)); ``` ![result](../_static/tutorial/result_keras.png) diff --git a/docs/source/tutorial/setup.md b/docs/source/tutorial/setup.md index f9f0f3366..f5b5c5e43 100644 --- a/docs/source/tutorial/setup.md +++ b/docs/source/tutorial/setup.md @@ -1,4 +1,6 @@ -# Setup Guide +# Setup Guide (for Mac / Linux) + +For Windows users, jump to [setup_windows](../setup_windows.html) ## Downloading code ``` @@ -30,7 +32,7 @@ If you want to convert models of Caffe or Chainer, install chainer package. pip install chainer ``` -(Currently, tested with `chainer==1.23`) +(Currently, tested with `chainer==2.0` and `chainer==1.23`) ## Installing Emscripten and Eigen If you want to enable WebAssembly backend, em++ command from [Emscripten](https://github.com/kripken/emscripten) is required. You can skip this section if you try WebGPU backend only. diff --git a/docs/source/tutorial/setup_windows.md b/docs/source/tutorial/setup_windows.md new file mode 100644 index 000000000..aced0feee --- /dev/null +++ b/docs/source/tutorial/setup_windows.md @@ -0,0 +1,97 @@ +# Setup Guide (for Windows) + +No browser on Windows supports WebGPU, but you can still develop applications using WebDNN. Commands have to be used on command prompt. + +## Downloading code +``` +git clone https://github.com/mil-tokyo/webdnn +``` + +If you do not have git, zip file is also available: [https://github.com/mil-tokyo/webdnn/archive/master.zip](https://github.com/mil-tokyo/webdnn/archive/master.zip) + +## Installing python environment +If you do not have python environment, install python environment. + +Anaconda is the popular installer: [https://www.continuum.io/downloads](https://www.continuum.io/downloads) + +When installing, adding PATH to system is optional. + +This framework requires python3.6+. This document is based on Anaconda 4.4.0. + +If you want to convert models of Caffe or Chainer, install chainer package. Refer to [Chainer document](http://docs.chainer.org/en/stable/install.html). + +(Currently, tested with `chainer==2.0` and `chainer==1.23`) + +## Installing Emscripten and Eigen +If you want to enable WebAssembly backend, em++ command from [Emscripten](https://github.com/kripken/emscripten) is required. You can skip this section if you try WebGPU backend only. + +To setup Emscripten which supports WebAssembly, + +Download "Emscripten SDK Offline Installer (emsdk-1.35.0-full-64bit.exe)" from [http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html). + +When installing, you should add PATH of `emcc` to PATH, but PATH of `python` should not be added (as it conflicts with Anaconda). + +[Eigen](http://eigen.tuxfamily.org) is needed as the library. Download latest source from [http://eigen.tuxfamily.org/index.php?title=Main_Page](http://eigen.tuxfamily.org/index.php?title=Main_Page) and decompress. + +## Setting for using proper python +python environment (3.6.x) of Anaconda have to be executed with `python3` command. To accompilsh it, create a file named `python3.bat` and fill with the following content. + +```bat +"C:\ProgramData\Anaconda3\python.exe" %* +``` + +python environment (2.7.x) of Anaconda have to be executed with `python` command. To accompilsh it, create a file named `python.bat` and fill with the following content. + +```bat +set CPLUS_INCLUDE_PATH=PATH\TO\EIGEN +"C:\Program Files\Emscripten\python\2.7.5.3_64bit\python.exe" %* +``` + +Concrete path depends on the version of Emscripten and installer configuration. `PATH\TO\EIGEN` have to be replaced by the directory where Eigen is downloaded. The file `PATH\TO\EIGEN\Eigen\Dense` should exists. + +## Verification of Emscripten and Eigen installation +Create a file named `hello.cpp`: + +```cpp +#include +#include + +int main() +{ + std::cout << "hello world" << std::endl; + return 0; +} + +``` + +Then, try to compile it into WebAssembly: + +```bat +em++ hello.cpp -O3 -s WASM=1 -o hello.html +``` + +If Emscripten works well, files such as `hello.wasm`, `hello.html` are generated. + +## Installing graph transpiler +Install graph transpiler for Anaconda python environment. + +At the top directory of WebDNN is cloned, type + +```bat +python3 setup.py install +``` + +Here, `python3.bat` have to exist on the current directory. + +## Running example + +```cmd +cd example\mnist +python3 train_mnist_chainer.py +``` + +Here, `python3.bat`, `python.bat` have to exist on the current directory. + +Graph descriptor files for each backend are generated. + +In Emscripten 1.35, files `before.js`, `load-wasm-worker.js` seem to be generated on current directory (it seems that latest Emscripten for linux does not require them). You have to move them to the output directory. diff --git a/example/README.md b/example/README.md index 7517e27f2..2288be63a 100644 --- a/example/README.md +++ b/example/README.md @@ -13,27 +13,32 @@ python -m http.server ## MNIST training and conversion `mnist` directory contains the example to train and convert a DNN for classifying the MNIST dataset. -This example illustrates how to use Chainer model for WebDNN. +This example illustrates how to use Keras and Chainer model for WebDNN. + +## ResNet-50 model conversion +`resnet` directory contains the example to convert the pre-trained ResNet-50 large-scale image classification DNN for WebDNN. + +This example illustrates how to use Keras and Chainer model for WebDNN. ## Caffe model conversion `convert_caffe` directory contains the example to convert the CaffeNet large-scale DNN for WebDNN. This example illustrates how to use Caffe model for WebDNN. -## Keras model conversion -`convert_keras` directory contains the example to train and convert a DNN for classifying the MNIST dataset. Additionally, it contains a instruction to convert the ResNet-50 large-scale DNN for WebDNN. +## Neural Style Transfer +`convert_neural_style_transfer` directory contains the example to convert the [Neural Style Transfer model](https://github.com/gafr/chainer-fast-neuralstyle-models) for WebDNN. -This example illustrates how to use Keras model for WebDNN. +This is intended to be example of interactive application. -## Chainer model conversion -`convert_chainer` directory contains the example to convert the ResNet-50 large-scale DNN for WebDNN. +## Sentence sentiment classification +`lstm` directory contains the example to train and convert LSTM-based model to classify sentiments of movie reviews. -This example illustrates how to use pre-trained Chainer model for WebDNN. +This example illustrates how to use LSTM model. -## Neural Style Transfer -`convert_neural_style_transfer` directory contains the example to convert the [Neural Style Transfer model](https://github.com/gafr/chainer-fast-neuralstyle-models) for WebDNN. +## Text generation +`text_generation` directory contains the example to convert LSTM-based text generation model to generate text like Nietzsche. -This is intended to be example of interactive application. +This example illustrates how to iteratively predict character probability and sample a character. ## Benchmark `benchmark` directory contains the speed benchmark to compare WebDNN and Keras.js. diff --git a/example/benchmark/README.md b/example/benchmark/README.md index e253df048..fb3a504d6 100644 --- a/example/benchmark/README.md +++ b/example/benchmark/README.md @@ -2,12 +2,32 @@ The benchmark for computing speed of DNN model on web browser +- Models + - ResNet50 + - VGG16 + - Inception-v3 + +- Execution type + - WebDNN (WebGPU + Optimization) + - WebDNN (WebGPU) + - WebDNN (WebAssembly + Optimization) + - WebDNN (WebAssembly) + - Keras.js (GPU) + - Keras.js (CPU) + ## How to run -First, generate Keras.js and WebDNN data files. +1. run `convert.sh`. -```shell -$ python ./convert.sh -``` + ```shell + $ ./convert.sh + ``` + + This script do follows things: + + - Clone Keras.js repository if need. + - Save Keras pretrained models + - Convert pretrained models into Keras.js format + - Convert pretrained models into WebDNN format -Then, open `index.html` on your browser +2. open `index.html` on your browser diff --git a/example/benchmark/convert.sh b/example/benchmark/convert.sh index 0ec6421c9..ac4349d1c 100755 --- a/example/benchmark/convert.sh +++ b/example/benchmark/convert.sh @@ -38,6 +38,7 @@ echo "\033[0;36m---------------------------------------------------------------- cd ./keras-js python encoder.py ../output/kerasjs/resnet50/model.h5 python encoder.py ../output/kerasjs/vgg16/model.h5 +python encoder.py ../output/kerasjs/inception_v3/model.h5 cd - echo "\033[0;36m----------------------------------------------------------------\033[0;39m" echo "\033[0;36m Encode Keras model into Keras.js model: \033[0;32mOK\033[0;39m" @@ -52,6 +53,8 @@ OPTIMIZE=0 python ../../bin/convert_keras.py output/kerasjs/resnet50/model.h5 -- OPTIMIZE=1 python ../../bin/convert_keras.py output/kerasjs/resnet50/model.h5 --input_shape '(1,224,224,3)' --out output/webdnn/resnet50/optimized OPTIMIZE=0 python ../../bin/convert_keras.py output/kerasjs/vgg16/model.h5 --input_shape '(1,224,224,3)' --out output/webdnn/vgg16/non_optimized OPTIMIZE=1 python ../../bin/convert_keras.py output/kerasjs/vgg16/model.h5 --input_shape '(1,224,224,3)' --out output/webdnn/vgg16/optimized +OPTIMIZE=0 python ../../bin/convert_keras.py output/kerasjs/inception_v3/model.h5 --input_shape '(1,299,299,3)' --out output/webdnn/inception_v3/non_optimized +OPTIMIZE=1 python ../../bin/convert_keras.py output/kerasjs/inception_v3/model.h5 --input_shape '(1,299,299,3)' --out output/webdnn/inception_v3/optimized echo "\033[0;36m----------------------------------------------------------------\033[0;39m" echo "\033[0;36m Encode Keras model into WebDNN model: \033[0;32mOK\033[0;39m" echo "\033[0;36m----------------------------------------------------------------\033[0;39m" diff --git a/example/benchmark/generate_keras_models.py b/example/benchmark/generate_keras_models.py index e52f49420..9a268b099 100755 --- a/example/benchmark/generate_keras_models.py +++ b/example/benchmark/generate_keras_models.py @@ -1,17 +1,27 @@ import os +from typing import Type -from keras.applications import VGG16 -from keras.applications import resnet50 +from keras.applications import ResNet50, VGG16, InceptionV3 +from keras.engine import Model -os.makedirs('output/kerasjs/resnet50', exist_ok=True) -os.makedirs('output/kerasjs/vgg16', exist_ok=True) -model = resnet50.ResNet50(include_top=True, weights='imagenet') -model.save('output/kerasjs/resnet50/model.h5') -with open('output/kerasjs/resnet50/model.json', 'w') as f: - f.write(model.to_json()) +def generate(NetworkModel: Type[Model], name: str, root_path: str, ): + if os.environ.get(f'NO_{name.upper()}', '0') == '1': + print(f'It is skipped to generate {name}' + + f'because environment variable "NO_{name.upper()}" is set') -model = VGG16(include_top=True, weights='imagenet') -model.save('output/kerasjs/vgg16/model.h5') -with open('output/kerasjs/vgg16/model.json', 'w') as f: - f.write(model.to_json()) + elif os.path.exists(f'{root_path}/model.h5') and os.path.exists(f'{root_path}/model.h5'): + print(f'{name} model data has been generated already in {root_path}/' + + 'model.{h5, json}. If you want regenerate it, please remove these files first.') + + else: + os.makedirs(root_path, exist_ok=True) + model = NetworkModel(include_top=True, weights='imagenet') + model.save(f'{root_path}/model.h5') + with open(f'{root_path}/model.json', 'w') as f: + f.write(model.to_json()) + + +generate(ResNet50, 'ResNet50', './output/kerasjs/resnet50') +generate(VGG16, 'VGG16', './output/kerasjs/vgg16') +generate(InceptionV3, 'InceptionV3', './output/kerasjs/inception_v3') diff --git a/example/benchmark/index.es5.html b/example/benchmark/index.es5.html index 26f8c5f60..be202dfd7 100644 --- a/example/benchmark/index.es5.html +++ b/example/benchmark/index.es5.html @@ -19,6 +19,7 @@

Benchmark (ES5 version)

Model: +
Framework and backends:
diff --git a/example/benchmark/index.es5.js b/example/benchmark/index.es5.js index a9404a92e..af576eae7 100644 --- a/example/benchmark/index.es5.js +++ b/example/benchmark/index.es5.js @@ -1,12 +1,50 @@ 'use strict'; -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); +var _createClass = function() { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + return function(Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } +function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + return call && (typeof call === "object" || typeof call === "function") ? call : self; +} -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } +function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +} + +function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} var benchmarks = []; @@ -23,7 +61,7 @@ function console_error(message) { document.getElementById('message').appendChild(document.createTextNode(message + "\n")); } -var BaseBenchmark = function () { +var BaseBenchmark = function() { function BaseBenchmark(name) { _classCallCheck(this, BaseBenchmark); @@ -51,11 +89,11 @@ var BaseBenchmark = function () { this.numIteration = numIteration; this.results = []; - return this.setupAsync().then(function () { + return this.setupAsync().then(function() { return _this.executeAsync(); - }).then(function () { + }).then(function() { return _this.finalizeAsync(); - }).then(function () { + }).then(function() { var summary = _this.summarize(); _this.summary = summary; @@ -77,12 +115,12 @@ var BaseBenchmark = function () { this.onExecuteSingle(this.results.length); var tStart = performance.now(); - return this.executeSingleAsync().then(function () { + return this.executeSingleAsync().then(function() { var elapsedTime = performance.now() - tStart; _this2.results.push(elapsedTime); - }).then(function () { - return new Promise(function (resolve, reject) { - setTimeout(function () { + }).then(function() { + return new Promise(function(resolve, reject) { + setTimeout(function() { resolve(_this2.executeAsync()); }, 10); }); @@ -103,11 +141,11 @@ var BaseBenchmark = function () { value: function summarize() { this.results.shift(); // remove first run var results = this.results.sort(); - var d = results.reduce(function (d, v) { + var d = results.reduce(function(d, v) { d.sum += v; d.sum2 += v * v; return d; - }, { sum: 0, sum2: 0 }); + }, {sum: 0, sum2: 0}); var mean = d.sum / results.length; var std = Math.sqrt((d.sum2 - results.length * mean * mean) / (results.length - 1)); @@ -129,7 +167,7 @@ var BaseBenchmark = function () { return BaseBenchmark; }(); -var KerasJSBenchmark = function (_BaseBenchmark) { +var KerasJSBenchmark = function(_BaseBenchmark) { _inherits(KerasJSBenchmark, _BaseBenchmark); function KerasJSBenchmark(name, flagGPU) { @@ -139,9 +177,6 @@ var KerasJSBenchmark = function (_BaseBenchmark) { _this3.model = null; _this3.flagGPU = flagGPU; - _this3.xs = { - 'input_1': new Float32Array(224 * 224 * 3) - }; return _this3; } @@ -158,6 +193,11 @@ var KerasJSBenchmark = function (_BaseBenchmark) { gpu: this.flagGPU }); + var HW = (modelName === 'inception_v3') ? (299 * 299) : (224 * 224); + this.xs = { + 'input_1': new Float32Array(HW * 3) + }; + return this.model.ready(); } }, { @@ -176,7 +216,7 @@ var KerasJSBenchmark = function (_BaseBenchmark) { return KerasJSBenchmark; }(BaseBenchmark); -var WebDNNBenchmark = function (_BaseBenchmark2) { +var WebDNNBenchmark = function(_BaseBenchmark2) { _inherits(WebDNNBenchmark, _BaseBenchmark2); function WebDNNBenchmark(name, backend, flagOptimized) { @@ -198,28 +238,26 @@ var WebDNNBenchmark = function (_BaseBenchmark2) { var _this5 = this; //noinspection ES6ModulesDependencies - return WebDNN.init([this.backend]).then(function () { + return WebDNN.init([this.backend]).then(function() { //noinspection ES6ModulesDependencies - _this5.runner = WebDNN.gpu.createDescriptorRunner(); - return _this5.runner.load('./output/webdnn/' + _this5.getSelectedModel() + '/' + (_this5.flagOptimized ? '' : 'non_') + 'optimized'); - }).then(function () { - return _this5.runner.getInputViews(); - }).then(function (xs) { - _this5.x = xs[0]; - return _this5.runner.getOutputViews(); - }).then(function (ys) { - _this5.y = ys[0]; + return WebDNN.runner.load('./output/webdnn/' + _this5.getSelectedModel() + '/' + (_this5.flagOptimized ? '' : 'non_') + 'optimized'); + }).then(function() { + return WebDNN.runner.getInputViews(); + }).then(function(xs) { + _this5.x = xs[0].toActual(); + return WebDNN.runner.getOutputViews(); + }).then(function(ys) { + _this5.y = ys[0].toActual(); }); } }, { key: 'executeSingleAsync', value: function executeSingleAsync() { - return this.runner.run(); + return WebDNN.runner.run(); } }, { key: 'finalizeAsync', value: function finalizeAsync() { - this.runner = null; this.x = null; this.y = null; } @@ -248,16 +286,16 @@ function run() { }; console_log('Benchmark ' + benchmark.name + ' start'); - Promise.resolve().then(function () { + Promise.resolve().then(function() { return benchmark.runAsync(numIteration).then(summaryHandler); - }).then(function () { + }).then(function() { return console_log('Benchmark end'); - }).catch(function (err) { + }).catch(function(err) { return console_error(err); }); } -document.addEventListener('DOMContentLoaded', function (event) { +document.addEventListener('DOMContentLoaded', function(event) { benchmarks.push(new WebDNNBenchmark('WebDNN(WebGPU) + Optimize', 'webgpu', true)); benchmarks.push(new WebDNNBenchmark('WebDNN(WebGPU)', 'webgpu', false)); benchmarks.push(new WebDNNBenchmark('WebDNN(WebAssembly) + Optimize', 'webassembly', true)); diff --git a/example/benchmark/index.html b/example/benchmark/index.html index 83679b69b..173176600 100644 --- a/example/benchmark/index.html +++ b/example/benchmark/index.html @@ -18,6 +18,7 @@

Benchmark

Model: +
Framework and backends:
diff --git a/example/benchmark/index.js b/example/benchmark/index.js index ba5c1deb2..f1cc1f674 100644 --- a/example/benchmark/index.js +++ b/example/benchmark/index.js @@ -90,7 +90,7 @@ class BaseBenchmark { d.sum += v; d.sum2 += v * v; return d; - }, { sum: 0, sum2: 0 }); + }, {sum: 0, sum2: 0}); let mean = d.sum / results.length; let std = Math.sqrt((d.sum2 - results.length * mean * mean) / (results.length - 1)); @@ -113,13 +113,11 @@ class KerasJSBenchmark extends BaseBenchmark { super(name); this.model = null; this.flagGPU = flagGPU; - this.xs = { - 'input_1': new Float32Array(224 * 224 * 3) - }; } setupAsync() { - let prefix = `./output/kerasjs/${this.getSelectedModel()}/model`; + let modelName = this.getSelectedModel(); + let prefix = `./output/kerasjs/${modelName}/model`; this.model = new KerasJS.Model({ filepaths: { model: `${prefix}.json`, @@ -129,6 +127,11 @@ class KerasJSBenchmark extends BaseBenchmark { gpu: this.flagGPU }); + const HW = (modelName === 'inception_v3') ? (299 * 299) : (224 * 224); + this.xs = { + 'input_1': new Float32Array(HW * 3) + }; + return this.model.ready() } @@ -145,28 +148,20 @@ class KerasJSBenchmark extends BaseBenchmark { class WebDNNBenchmark extends BaseBenchmark { constructor(name, backend, flagOptimized) { super(name); - this.runner = null; this.x = null; this.y = null; this.backend = backend; this.flagOptimized = flagOptimized; + this.runner = null; } setupAsync() { //noinspection ES6ModulesDependencies - return WebDNN.init([this.backend]) - .then(() => { - //noinspection ES6ModulesDependencies - this.runner = WebDNN.gpu.createDescriptorRunner(); - return this.runner.load(`./output/webdnn/${this.getSelectedModel()}/${this.flagOptimized ? '' : 'non_'}optimized`); - }) - .then(() => this.runner.getInputViews()) - .then(xs => { - this.x = xs[0]; - return this.runner.getOutputViews(); - }) - .then(ys => { - this.y = ys[0]; + return WebDNN.load(`./output/webdnn/${this.getSelectedModel()}/${this.flagOptimized ? '' : 'non_'}optimized`, {backendOrder: [this.backend]}) + .then((runner) => { + this.runner = runner; + this.x = runner.getInputViews()[0].toActual(); + this.y = runner.getOutputViews()[0].toActual(); }) } @@ -175,9 +170,9 @@ class WebDNNBenchmark extends BaseBenchmark { } finalizeAsync() { - this.runner = null; this.x = null; this.y = null; + this.runner = null; } } @@ -206,7 +201,7 @@ function run() { .catch(err => console_error(err)); } -document.addEventListener('DOMContentLoaded', function (event) { +document.addEventListener('DOMContentLoaded', function(event) { benchmarks.push(new WebDNNBenchmark('WebDNN(WebGPU) + Optimize', 'webgpu', true)); benchmarks.push(new WebDNNBenchmark('WebDNN(WebGPU)', 'webgpu', false)); benchmarks.push(new WebDNNBenchmark('WebDNN(WebAssembly) + Optimize', 'webassembly', true)); diff --git a/example/convert_caffe/descriptor_run_caffenet.js b/example/convert_caffe/descriptor_run_caffenet.js index d7726cbdc..e3aca4a9e 100644 --- a/example/convert_caffe/descriptor_run_caffenet.js +++ b/example/convert_caffe/descriptor_run_caffenet.js @@ -33,24 +33,24 @@ function load_image() { img.src = document.querySelector("input[name=image_url]").value; } -let run_ifs = {}; +let runners = {}; async function prepare_run() { let backend_name = document.querySelector('input[name=backend_name]:checked').value; - if (!(backend_name in run_ifs)) { + if (!(backend_name in runners)) { log('Initializing and loading model'); - let run_if = await WebDNN.prepareAll('./output', { backendOrder: backend_name }); - log(`Loaded backend: ${run_if.backendName}`); + let runner = await WebDNN.load('./output', { backendOrder: backend_name }); + log(`Loaded backend: ${runner.backendName}`); - run_ifs[backend_name] = run_if; + runners[backend_name] = runner; } else { log('Model is already loaded'); } - return run_ifs[backend_name]; + return runners[backend_name]; } async function run() { - let run_if = await prepare_run(); + let runner = await prepare_run(); let total_elapsed_time = 0; let pred_label; @@ -60,13 +60,13 @@ async function run() { log('Running model...'); for (let i = 0; i < test_samples.length; i++) { let sample = test_samples[i]; - run_if.inputViews[0].set(sample); + runner.getInputViews()[0].set(sample); let start = performance.now(); - await run_if.run(); + await runner.run(); total_elapsed_time += performance.now() - start; - let out_vec = run_if.outputViews[0]; + let out_vec = runner.getOutputViews()[0].toActual(); let top_labels = WebDNN.Math.argmax(out_vec, 5); let predicted_str = 'Predicted:'; for (let j = 0; j < top_labels.length; j++) { @@ -75,7 +75,7 @@ async function run() { log(predicted_str); console.log('output vector: ', out_vec); } - log(`Total Elapsed Time[ms/image]: ${(total_elapsed_time / test_samples.length).toFixed(2)}, backend=${run_if.backendName}`); + log(`Total Elapsed Time[ms/image]: ${(total_elapsed_time / test_samples.length).toFixed(2)}, backend=${runner.backendName}`); } function makeMatFromJson(mat_data) { diff --git a/example/convert_caffe/minimal.js b/example/convert_caffe/minimal.js index d2ea97b0f..e70e7fe70 100644 --- a/example/convert_caffe/minimal.js +++ b/example/convert_caffe/minimal.js @@ -5,11 +5,11 @@ let x, y; async function init() { // (1.1) Initialize DescriptorRunner - runner = await WebDNN.prepareAll('./output'); + runner = await WebDNN.load('./output'); // (1.2) Get input and output variables - x = runner.inputViews[0]; - y = runner.outputViews[0]; + x = runner.getInputViews()[0].toActual(); + y = runner.getOutputViews()[0].toActual(); } async function run() { diff --git a/example/convert_keras/README.md b/example/convert_keras/README.md deleted file mode 100644 index da64b9d32..000000000 --- a/example/convert_keras/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Keras model conversion example -Our scripts assume Keras version 2.0.x. - -## MNIST MLP model -### 1. Train model in Keras -```sh -python mnist_mlp.py -``` - -### 2. Convert model into WebDNN graph descriptor -```sh -python ../../bin/convert_keras.py output_mnist/keras_model/mnist_mlp.h5 --input_shape '(1,784)' --out output_mnist -``` - -The "input_shape" option is the shape of input array. First "1" is the batch size, and the following numbers are the shape of a sample. - -### 3. Use in the web browser -You can now use the model on the web browser -[descriptor_run_mnist.html](http://localhost:8000/example/convert_keras/descriptor_run_mnist.html) via a HTTP server. - - -## MNIST Convolutional model -```sh -python mnist_mlp.py --model conv -``` - -Trains convolutional model. - - -### 2. Convert model into WebDNN graph descriptor -For conv model, the shape of input is (1, 28, 28, 1) where (batchsize, h, w, channels). This order depends on the training backend setting (data_format). Currently, WebDNN converter supports only when `data_format==channels_last`. - -```sh -python ../../bin/convert_keras.py output_mnist/keras_model/mnist_mlp.h5 --input_shape '(1,28,28,1)' --out output_mnist -``` - -### 3. Use in the web browser -You can now use the model on the web browser -[descriptor_run_mnist.html](http://localhost:8000/example/convert_keras/descriptor_run_mnist.html) via a HTTP server. - -## ResNet-50 model -### 1. Exporting Keras sample model -In ipython, run the following statements. - -```python -from keras.applications import resnet50 -model = resnet50.ResNet50(include_top=True, weights='imagenet') -model.save("resnet50.h5") -``` - -### 2. Convert model into WebDNN graph descriptor -```sh -python ../../bin/convert_keras.py resnet50.h5 --input_shape '(1,224,224,3)' --out output -``` - -### 3. Use in the web browser -You can now use the model on the web browser -[descriptor_run_resnet.html](http://localhost:8000/example/convert_keras/descriptor_run_resnet.html) via a HTTP server. diff --git a/example/convert_neural_style_transfer/convert.py b/example/convert_neural_style_transfer/convert.py index 4030a7540..df7cedabd 100644 --- a/example/convert_neural_style_transfer/convert.py +++ b/example/convert_neural_style_transfer/convert.py @@ -8,7 +8,7 @@ from model import FastStyleNet from webdnn.backend.interface.generator import generate_descriptor -from webdnn.graph.converters.chainer import ChainerGraphConverter +from webdnn.frontend.chainer import ChainerConverter class NSTModelPath(Enum): @@ -43,10 +43,10 @@ class NSTModelPath(Enum): # Execute forward propagation to construct computation graph x = chainer.Variable(np.zeros((1, 3, 144, 192), dtype=np.float32)) -y = model(x, test=True) +y = model(x) # Convert chainer computation graph into IR -graph = ChainerGraphConverter().convert_from_inout_vars([x], [y]) +graph = ChainerConverter().convert_from_inout_vars([x], [y]) # Generate graph descriptor generate_descriptor(args.backend, graph, constant_encoder_name=args.encoding).save(path.join(path.dirname(__file__), "./output")) diff --git a/example/convert_neural_style_transfer/model.py b/example/convert_neural_style_transfer/model.py index 8c5b6a085..d3f51904b 100755 --- a/example/convert_neural_style_transfer/model.py +++ b/example/convert_neural_style_transfer/model.py @@ -33,65 +33,132 @@ import chainer.functions as F import chainer.links as L +if chainer.__version__ >= "2.": + chainer.using_config("train", False) -class ResidualBlock(chainer.Chain): - def __init__(self, n_in, n_out, stride=1, ksize=3): - w = math.sqrt(2) - super(ResidualBlock, self).__init__( - c1=L.Convolution2D(n_in, n_out, ksize, stride, 1, w), - c2=L.Convolution2D(n_out, n_out, ksize, 1, 1, w), - b1=L.BatchNormalization(n_out), - b2=L.BatchNormalization(n_out) - ) - - # noinspection PyCallingNonCallable, PyUnresolvedReferences - def __call__(self, x, test): - h = F.relu(self.b1(self.c1(x), test=test)) - h = self.b2(self.c2(h), test=test) - if x.data.shape != h.data.shape: - xp = chainer.cuda.get_array_module(x.data) - n, c, hh, ww = x.data.shape - pad_c = h.data.shape[1] - c - p = xp.zeros((n, pad_c, hh, ww), dtype=xp.float32) - p = chainer.Variable(p, volatile=test) - x = F.concat((p, x)) - if x.data.shape[2:] != h.data.shape[2:]: - x = F.average_pooling_2d(x, 1, 2) - return h + x - - -class FastStyleNet(chainer.Chain): - def __init__(self): - super(FastStyleNet, self).__init__( - c1=L.Convolution2D(3, 32, 9, stride=1, pad=4), - c2=L.Convolution2D(32, 64, 4, stride=2, pad=1), - c3=L.Convolution2D(64, 128, 4, stride=2, pad=1), - r1=ResidualBlock(128, 128), - r2=ResidualBlock(128, 128), - r3=ResidualBlock(128, 128), - r4=ResidualBlock(128, 128), - r5=ResidualBlock(128, 128), - d1=L.Deconvolution2D(128, 64, 4, stride=2, pad=1), - d2=L.Deconvolution2D(64, 32, 4, stride=2, pad=1), - d3=L.Deconvolution2D(32, 3, 9, stride=1, pad=4), - b1=L.BatchNormalization(32), - b2=L.BatchNormalization(64), - b3=L.BatchNormalization(128), - b4=L.BatchNormalization(64), - b5=L.BatchNormalization(32), - ) - - # noinspection PyCallingNonCallable, PyUnresolvedReferences - def __call__(self, x, test=False): - h = self.b1(F.elu(self.c1(x)), test=test) - h = self.b2(F.elu(self.c2(h)), test=test) - h = self.b3(F.elu(self.c3(h)), test=test) - h = self.r1(h, test=test) - h = self.r2(h, test=test) - h = self.r3(h, test=test) - h = self.r4(h, test=test) - h = self.r5(h, test=test) - h = self.b4(F.elu(self.d1(h)), test=test) - h = self.b5(F.elu(self.d2(h)), test=test) - y = self.d3(h) - return (F.tanh(y) + 1) * 127.5 + + class ResidualBlock(chainer.Chain): + def __init__(self, n_in, n_out, stride=1, ksize=3): + w = math.sqrt(2) + super(ResidualBlock, self).__init__( + c1=L.Convolution2D(n_in, n_out, ksize, stride, 1, initialW=chainer.initializers.Normal(w / math.sqrt(n_in))), + c2=L.Convolution2D(n_out, n_out, ksize, 1, 1, initialW=chainer.initializers.Normal(w / math.sqrt(n_in))), + b1=L.BatchNormalization(n_out), + b2=L.BatchNormalization(n_out) + ) + + # noinspection PyCallingNonCallable, PyUnresolvedReferences + def __call__(self, x): + h = F.relu(self.b1(self.c1(x))) + h = self.b2(self.c2(h)) + if x.data.shape != h.data.shape: + xp = chainer.cuda.get_array_module(x.data) + n, c, hh, ww = x.data.shape + pad_c = h.data.shape[1] - c + p = xp.zeros((n, pad_c, hh, ww), dtype=xp.float32) + p = chainer.Variable(p) + x = F.concat((p, x)) + if x.data.shape[2:] != h.data.shape[2:]: + x = F.average_pooling_2d(x, 1, 2) + return h + x + + + class FastStyleNet(chainer.Chain): + def __init__(self): + super(FastStyleNet, self).__init__( + c1=L.Convolution2D(3, 32, 9, stride=1, pad=4), + c2=L.Convolution2D(32, 64, 4, stride=2, pad=1), + c3=L.Convolution2D(64, 128, 4, stride=2, pad=1), + r1=ResidualBlock(128, 128), + r2=ResidualBlock(128, 128), + r3=ResidualBlock(128, 128), + r4=ResidualBlock(128, 128), + r5=ResidualBlock(128, 128), + d1=L.Deconvolution2D(128, 64, 4, stride=2, pad=1), + d2=L.Deconvolution2D(64, 32, 4, stride=2, pad=1), + d3=L.Deconvolution2D(32, 3, 9, stride=1, pad=4), + b1=L.BatchNormalization(32), + b2=L.BatchNormalization(64), + b3=L.BatchNormalization(128), + b4=L.BatchNormalization(64), + b5=L.BatchNormalization(32), + ) + + # noinspection PyCallingNonCallable, PyUnresolvedReferences + def __call__(self, x): + h = self.b1(F.elu(self.c1(x))) + h = self.b2(F.elu(self.c2(h))) + h = self.b3(F.elu(self.c3(h))) + h = self.r1(h) + h = self.r2(h) + h = self.r3(h) + h = self.r4(h) + h = self.r5(h) + h = self.b4(F.elu(self.d1(h))) + h = self.b5(F.elu(self.d2(h))) + y = self.d3(h) + return (F.tanh(y) + 1) * 127.5 + +else: + + class ResidualBlock(chainer.Chain): + def __init__(self, n_in, n_out, stride=1, ksize=3): + w = math.sqrt(2) + super(ResidualBlock, self).__init__( + c1=L.Convolution2D(n_in, n_out, ksize, stride, 1, w), + c2=L.Convolution2D(n_out, n_out, ksize, 1, 1, w), + b1=L.BatchNormalization(n_out), + b2=L.BatchNormalization(n_out) + ) + + # noinspection PyCallingNonCallable, PyUnresolvedReferences + def __call__(self, x, test): + h = F.relu(self.b1(self.c1(x), test=test)) + h = self.b2(self.c2(h), test=test) + if x.data.shape != h.data.shape: + xp = chainer.cuda.get_array_module(x.data) + n, c, hh, ww = x.data.shape + pad_c = h.data.shape[1] - c + p = xp.zeros((n, pad_c, hh, ww), dtype=xp.float32) + p = chainer.Variable(p, volatile=test) + x = F.concat((p, x)) + if x.data.shape[2:] != h.data.shape[2:]: + x = F.average_pooling_2d(x, 1, 2) + return h + x + + + class FastStyleNet(chainer.Chain): + def __init__(self): + super(FastStyleNet, self).__init__( + c1=L.Convolution2D(3, 32, 9, stride=1, pad=4), + c2=L.Convolution2D(32, 64, 4, stride=2, pad=1), + c3=L.Convolution2D(64, 128, 4, stride=2, pad=1), + r1=ResidualBlock(128, 128), + r2=ResidualBlock(128, 128), + r3=ResidualBlock(128, 128), + r4=ResidualBlock(128, 128), + r5=ResidualBlock(128, 128), + d1=L.Deconvolution2D(128, 64, 4, stride=2, pad=1), + d2=L.Deconvolution2D(64, 32, 4, stride=2, pad=1), + d3=L.Deconvolution2D(32, 3, 9, stride=1, pad=4), + b1=L.BatchNormalization(32), + b2=L.BatchNormalization(64), + b3=L.BatchNormalization(128), + b4=L.BatchNormalization(64), + b5=L.BatchNormalization(32), + ) + + # noinspection PyCallingNonCallable, PyUnresolvedReferences + def __call__(self, x, test=True): + h = self.b1(F.elu(self.c1(x)), test=test) + h = self.b2(F.elu(self.c2(h)), test=test) + h = self.b3(F.elu(self.c3(h)), test=test) + h = self.r1(h, test=test) + h = self.r2(h, test=test) + h = self.r3(h, test=test) + h = self.r4(h, test=test) + h = self.r5(h, test=test) + h = self.b4(F.elu(self.d1(h)), test=test) + h = self.b5(F.elu(self.d2(h)), test=test) + y = self.d3(h) + return (F.tanh(y) + 1) * 127.5 diff --git a/example/convert_neural_style_transfer/script.js b/example/convert_neural_style_transfer/script.js index 1cf0579c2..a4481ef50 100644 --- a/example/convert_neural_style_transfer/script.js +++ b/example/convert_neural_style_transfer/script.js @@ -29,10 +29,8 @@ function togglePause() { async function initialize() { try { //noinspection ES6ModulesDependencies - let backend = await WebDNN.init(); - console.log(`backend: ${backend}`); - - dnn = await WebDNN.prepareAll("./output"); + dnn = await WebDNN.load("./output"); + console.log(`backend: ${dnn.backendName}`); Webcam.set({ dest_width: 192, @@ -50,8 +48,8 @@ async function initialize() { $input = $('#snap'); $output = $('#result'); - inputView = dnn.inputViews[0]; - outputView = dnn.outputViews[0]; + inputView = dnn.getInputViews()[0].toActual(); + outputView = dnn.getOutputViews()[0].toActual(); ctxIn = $input.getContext('2d'); ctxOut = $output.getContext('2d'); h = $output.height; diff --git a/example/convert_resnet/descriptor_run_resnet.html b/example/convert_resnet/descriptor_run_resnet.html deleted file mode 100644 index 574739bcc..000000000 --- a/example/convert_resnet/descriptor_run_resnet.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - ResNet-50 conversion WebDNN example - - - - - - - - -

ResNet-50 conversion WebDNN example

- You have to convert ResNet-50 with python script before running this webpage. -
- Image URL:
-
-
- Using canvas's image slightly changes the output due to interpolation.
- - - - -
-
- -
- - - diff --git a/example/convert_resnet/descriptor_run_resnet.js b/example/convert_resnet/descriptor_run_resnet.js deleted file mode 100644 index 8874a70ef..000000000 --- a/example/convert_resnet/descriptor_run_resnet.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; - -var is_image_loaded = false; - -function run_entry() { - run().then(() => { - log('Run finished'); - }).catch((error) => { - log('Error: ' + error); - }); -} - -function log(msg) { - let msg_node = document.getElementById('messages'); - msg_node.appendChild(document.createElement('br')); - msg_node.appendChild(document.createTextNode(msg)); -} - -function load_image() { - var img = new Image(); - img.onload = function () { - var ctx = document.getElementById('input_image').getContext('2d'); - // shrink instead of crop - ctx.drawImage(img, 0, 0, 224, 224); - is_image_loaded = true; - document.getElementById('run_button').disabled = false; - log('Image loaded to canvas'); - } - img.onerror = function () { - log('Failed to load image'); - } - img.src = document.querySelector("input[name=image_url]").value; -} - -let test_samples; -let run_ifs = {}; - -async function prepare_run() { - let backend_name = document.querySelector('input[name=backend_name]:checked').value; - if (!(backend_name in run_ifs)) { - log('Initializing and loading model'); - let run_if = await WebDNN.prepareAll('./output', { backendOrder: backend_name }); - log(`Loaded backend: ${run_if.backendName}`); - - run_ifs[backend_name] = run_if; - } else { - log('Model is already loaded'); - } - return run_ifs[backend_name]; -} - -async function run() { - let run_if = await prepare_run(); - - let test_image = getImageData(); - let test_samples = [test_image]; - - let total_elapsed_time = 0; - let pred_label; - for (let i = 0; i < test_samples.length; i++) { - let sample = test_samples[i]; - run_if.inputViews[0].set(sample); - - let start = performance.now(); - await run_if.run(); - total_elapsed_time += performance.now() - start; - - let out_vec = run_if.outputViews[0]; - let top_labels = WebDNN.Math.argmax(out_vec, 5); - let predicted_str = 'Predicted:'; - for (let j = 0; j < top_labels.length; j++) { - predicted_str += ` ${top_labels[j]}(${imagenet_labels[top_labels[j]]})`; - } - log(predicted_str); - console.log('output vector: ', out_vec); - } - log(`Total Elapsed Time[ms/image]: ${(total_elapsed_time / test_samples.length).toFixed(2)}`); -} - -function makeMatFromJson(mat_data) { - var mat = new Float32Array(mat_data['data']); - return mat; -} - -async function fetchImage(path) { - let response = await fetch(path); - let json = await response.json(); - - return new Float32Array(json); -} - -function getImageData() { - let ctx = document.getElementById('input_image').getContext('2d'); - let h = 224; - let w = 224; - let imagedata = ctx.getImageData(0, 0, h, w);//h,w,c(rgba) - let pixeldata = imagedata.data; - let data = new Float32Array(3 * h * w);//h,w,c(bgr) - for (let y = 0; y < h; y++) { - for (let x = 0; x < w; x++) { - data[(y * w + x) * 3] = pixeldata[(y * w + x) * 4 + 2] - 103.939;//b - data[(y * w + x) * 3 + 1] = pixeldata[(y * w + x) * 4 + 1] - 116.779;//g - data[(y * w + x) * 3 + 2] = pixeldata[(y * w + x) * 4 + 0] - 123.68;//r - } - } - return data; -} diff --git a/example/convert_keras/descriptor_run_mnist.html b/example/lstm/descriptor_run_imdb.html similarity index 54% rename from example/convert_keras/descriptor_run_mnist.html rename to example/lstm/descriptor_run_imdb.html index 969f4a6f2..fedd132f0 100644 --- a/example/convert_keras/descriptor_run_mnist.html +++ b/example/lstm/descriptor_run_imdb.html @@ -2,26 +2,28 @@ - WebDNN mnist example + WebDNN LSTM sequence classification example - + - WebDNN mnist example + WebDNN LSTM sequence classification example
- +
+ Backend: - - +
+ Framework for model: +
- +
ImagePredicted labelGround truth
SentencePredicted sentiment (Positive=1, Negative=0)Ground truth
- \ No newline at end of file + diff --git a/example/convert_keras/descriptor_run_mnist.js b/example/lstm/descriptor_run_imdb.js similarity index 54% rename from example/convert_keras/descriptor_run_mnist.js rename to example/lstm/descriptor_run_imdb.js index 72d0e6583..c6f2d86b2 100644 --- a/example/convert_keras/descriptor_run_mnist.js +++ b/example/lstm/descriptor_run_imdb.js @@ -1,6 +1,7 @@ 'use strict'; -let run_if = null; +let runner = null; +let test_samples = null; function msg(s) { document.getElementById('msg_place').textContent = s; @@ -16,57 +17,53 @@ function run_entry() { } function reset_backend() { - run_if = null; + runner = null; resetOutputTable(document.getElementById('result')); msg('Resetted backend'); } async function run() { - if (!run_if) { + if (!runner) { let backend_name = document.querySelector('input[name=backend_name]:checked').value; - run_if = await WebDNN.prepareAll('./output_mnist', { backendOrder: backend_name }); - msg(`Backend: ${run_if.backendName}`); - console.info(`Backend: ${run_if.backendName}`); + let framework_name = document.querySelector('input[name=framework_name]:checked').value; + runner = await WebDNN.load(`./output_${framework_name}`, { backendOrder: backend_name }); + msg(`Backend: ${runner.backendName}, model converted from ${framework_name}`); + console.info(`Backend: ${runner.backendName}, model converted from ${framework_name}`); + test_samples = await fetchSamples(`./output_${framework_name}/imdb_lstm_samples.json`); } - let test_samples = await fetchSamples('./output_mnist/test_samples.json'); let output_table = document.getElementById('result'); resetOutputTable(output_table); let total_elapsed_time = 0; for (let i = 0; i < test_samples.length; i++) { let sample = test_samples[i]; - run_if.inputViews[0].set(sample.x); + runner.getInputViews()[0].set(sample.x); console.log(`ground truth: ${sample.y}`); + console.log(`original model output: ${sample.orig_pred}`); let start = performance.now(); - await run_if.run(); + await runner.run(); total_elapsed_time += performance.now() - start; - let out_vec = run_if.outputViews[0]; - let pred_label = WebDNN.Math.argmax(out_vec)[0]; - // equivalent to - /* let pred_label = 0; - let pred_score = -Infinity; - for (let j = 0; j < out_vec.length; j++) { - if (out_vec[j] > pred_score) { - pred_score = out_vec[j]; - pred_label = j; - } - }*/ - console.log(`predicted: ${pred_label}`); + let out_vec = runner.getOutputViews()[0].toActual(); + let pred_value = out_vec[0]; + console.log(`predicted: ${pred_value}`); console.log(out_vec); - displayPrediction(output_table, sample.x, pred_label, sample.y); + displayPrediction(output_table, sample.orig_sentence, pred_value, sample.y); } console.log(`Total Elapsed Time[ms/image]: ${(total_elapsed_time / test_samples.length).toFixed(2)}`); } async function fetchSamples(path) { let response = await fetch(path); + if (!response.ok) { + throw new Error('Failed to load test samples'); + } let json = await response.json(); let samples = []; for (let i = 0; i < json.length; i++) { - samples.push({ 'x': new Float32Array(json[i]['x']), 'y': json[i]['y'] }); + samples.push({ 'x': new Float32Array(json[i]['x']), 'y': json[i]['y'], 'orig_sentence': json[i]['orig_sentence'], 'orig_pred': json[i]['orig_pred'] }); } return samples; @@ -81,20 +78,7 @@ function resetOutputTable(table) { function displayPrediction(table, input_image, prediction, ground_truth) { let tr = document.createElement('tr'); - let canvas = document.createElement('canvas'); - canvas.width = 28; - canvas.height = 28; - let ctx = canvas.getContext('2d'); - let img = ctx.createImageData(28, 28); - for (let i = 0; i < 28 * 28; i++) { - let pixel = input_image[i] * 255; - img.data[i * 4 + 0] = pixel;//r - img.data[i * 4 + 1] = pixel;//g - img.data[i * 4 + 2] = pixel;//b - img.data[i * 4 + 3] = 255;//a - } - ctx.putImageData(img, 0, 0); - tr.appendChild(document.createElement('td')).appendChild(canvas); + tr.appendChild(document.createElement('td')).textContent = '' + input_image; tr.appendChild(document.createElement('td')).textContent = '' + prediction; tr.appendChild(document.createElement('td')).textContent = '' + ground_truth; diff --git a/example/lstm/imdb_lstm.py b/example/lstm/imdb_lstm.py new file mode 100644 index 000000000..0283afafa --- /dev/null +++ b/example/lstm/imdb_lstm.py @@ -0,0 +1,131 @@ +''' +This example is based on Keras's example. + +Trains a LSTM on the IMDB sentiment classification task. +The dataset is actually too small for LSTM to be of any advantage +compared to simpler, much faster methods such as TF-IDF + LogReg. +Notes: + +- RNNs are tricky. Choice of batch size is important, +choice of loss and optimizer is critical, etc. +Some configurations won't converge. + +- LSTM loss decrease patterns during training can be quite different +from what you see with CNNs/MLPs/etc. +''' + +import argparse +import sys +import os +from os import path +import subprocess +import json + +from keras.preprocessing import sequence +from keras.models import Sequential +from keras.layers import Dense, Embedding, ZeroPadding1D +from keras.layers import LSTM +from keras.datasets import imdb + +max_features = 20000 +maxlen = 80 # cut texts after this number of words (among top max_features most common words) +batch_size = 32 +word_index_table = None + + +def word_index_to_sentence(idxs): + global word_index_table + if word_index_table is None: + raw_index = imdb.get_word_index() # {"the": 1, "and": 2, ...} + word_index_table = {v + 3: k for k, v in raw_index.items()} # {4: "the", 5: "and", ...} + word_index_table[0] = "" # padding + word_index_table[1] = "" # start of sentence + word_index_table[2] = "_" # out of vocabulary + word_index_table[3] = "" # ? + return " ".join(map(lambda idx: word_index_table[idx], idxs)) + + +def train_model(save_path, sample_output_path): + print('Loading data...') + (x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features) + print(len(x_train), 'train sequences') + print(len(x_test), 'test sequences') + + print('Pad sequences (samples x time)') + x_train = sequence.pad_sequences(x_train, maxlen=maxlen) + x_test = sequence.pad_sequences(x_test, maxlen=maxlen) + print('x_train shape:', x_train.shape) + print('x_test shape:', x_test.shape) + + print('Build model...') + model = Sequential() + model.add(Embedding(max_features, 128)) + # model.add(ZeroPadding1D(padding=(0,1))) + model.add(LSTM(64, dropout=0.2, recurrent_dropout=0.2, return_sequences=True)) # stacked lstm + model.add(LSTM(32, dropout=0.2, recurrent_dropout=0.2)) + model.add(Dense(1, activation='sigmoid')) + + # try using different optimizers and different optimizer configs + model.compile(loss='binary_crossentropy', + optimizer='adam', + metrics=['accuracy']) + + print('Train...') + model.fit(x_train, y_train, + batch_size=batch_size, + epochs=1, + validation_data=(x_test, y_test)) + score, acc = model.evaluate(x_test, y_test, + batch_size=batch_size) + print('Test score:', score) + print('Test accuracy:', acc) + + model.save(save_path) + + print("Exporting test samples (for demo purpose)") + export_size = 10 + x_export = x_test[:export_size] + y_export = y_test[:export_size] + orig_pred_export = model.predict(x_export, batch_size=batch_size) + test_samples_json = [] + for i in range(export_size): + word_index_list = x_export[i].tolist() + text_sentence = word_index_to_sentence(word_index_list) + test_samples_json.append({"x": word_index_list, "y": int(y_export[i]), + "orig_pred": float(orig_pred_export[i, 0]), "orig_sentence": text_sentence}) + + with open(sample_output_path, "w") as f: + json.dump(test_samples_json, f) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--out', '-o', default='output_keras', + help='Directory to output the graph descriptor') + parser.add_argument("--encoding", help="name of weight encoder") + + args = parser.parse_args() + + os.makedirs(args.out, exist_ok=True) + + original_model_path = path.join(args.out, "imdb_lstm.h5") + if not path.exists(original_model_path): + print("Training model") + train_model(original_model_path, path.join(args.out, "imdb_lstm_samples.json")) + else: + print("Use already trained model") + + print("Converting model into WebDNN format (graph descriptor)") + # only for demo purpose, maybe not safe + convert_keras_command = f"python ../../bin/convert_keras.py {original_model_path} --input_shape '(1,{maxlen})' --out {args.out} --backend webgpu,webassembly" + if args.encoding: + convert_keras_command += f" --encoding {args.encoding}" + print("$ " + convert_keras_command) + + subprocess.check_call(convert_keras_command, shell=True) + + print("Done.") + + +if __name__ == "__main__": + main() diff --git a/example/mnist/README.md b/example/mnist/README.md index 6cfc76723..bd6f49739 100644 --- a/example/mnist/README.md +++ b/example/mnist/README.md @@ -1,5 +1,45 @@ # MNIST model conversion sample -This example first trains MNIST classification model with Chainer, then transpiles it to WebDNN format (graph descriptor). +This directory contains examples of training MNIST classification model and transpiling it to WebDNN format (graph descriptor). + +## Keras +Our scripts assume Keras version 2.0.x. + +To run this example, you need to install Keras. + +Train and convert MNIST classification model at once: +``` +python train_mnist_keras.py --model fc +``` + +For `model` argument, `fc` (fully-connected) and `conv` (convolution) can be selected. + +In fact, the script contains two steps: + +1. Train model in Keras + This is ordinary example of Keras. The model is saved to `output_keras/keras_model/mnist_mlp.h5`. + +2. Executing converting command + ```sh + python ../../bin/convert_keras.py output_keras/keras_model/mnist_mlp.h5 --input_shape '(1,784)' --out output_keras + ``` + + The "input_shape" option is the shape of input array. First "1" is the batch size, and the following numbers are the shape of a sample. + For convolution model, it changes to `(1,28,28,1)` where `(batchsize, height, width, channel)`. This order depends on the training backend setting (data_format). Currently, WebDNN converter supports only when `data_format==channels_last`. + +## PyTorch + +To run this example, you need to install Chainer. +``` +pip install pytorch +pip install torchvision +``` + +Train and convert MNIST classification model +``` +python train_mnist_pytorch.py +``` + +## Chainer To run this example, you need to install Chainer. ``` @@ -8,10 +48,12 @@ pip install chainer Train and convert MNIST classification model ``` -python train_mnist.py +python train_mnist_chainer.py ``` -Then start a HTTP server on the package root directory (where `setup.py` exists) +## Running on the web browser +Start a HTTP server on the package root directory (where `setup.py` exists) + ``` python -m http.server ``` diff --git a/example/mnist/descriptor_run_mnist.es5.html b/example/mnist/descriptor_run_mnist.es5.html index 3da9474d5..553304eac 100644 --- a/example/mnist/descriptor_run_mnist.es5.html +++ b/example/mnist/descriptor_run_mnist.es5.html @@ -5,7 +5,8 @@ WebDNN mnist example - + + @@ -14,11 +15,16 @@ WebDNN mnist example (ES5 compliant; works with IE11)
- +
+ Backend: - +
+ Framework for model: + + +
diff --git a/example/mnist/descriptor_run_mnist.es5.js b/example/mnist/descriptor_run_mnist.es5.js index 6a26359bd..cf6a6d232 100644 --- a/example/mnist/descriptor_run_mnist.es5.js +++ b/example/mnist/descriptor_run_mnist.es5.js @@ -1,108 +1,178 @@ 'use strict'; -// Polyfill of Promise, fetch is needed -// manually written for ES5 environment - -var run_if = null; - -function msg(s) { - document.getElementById('msg_place').textContent = s; -} - -function reset_backend() { - run_if = null; - resetOutputTable(document.getElementById('result')); - msg('Resetted backend'); -} - -function run() { - if (!run_if) { - let backend_name = document.querySelector('input[name=backend_name]:checked').value; - WebDNN.prepareAll('./output', { backendOrder: backend_name }).then(function (result) { - run_if = result; - msg('Backend: ' + run_if.backendName); - console.info('Backend: ' + run_if.backendName); - run(); - }).catch(function (reason) { - console.error('Initialization failed: ' + reason); - }); - return; - } - - var output_table = document.getElementById('result'); - resetOutputTable(output_table); - fetchSamples('./output/test_samples.json').then(function (result) { - var test_samples = result; - var total_elapsed_time = 0; - var i = 0; - var sample; - var start; - var loop_code = function () { - if (i >= test_samples.length) { - console.log('Total Elapsed Time[ms/image]: ' + (total_elapsed_time / test_samples.length).toFixed(2)); - return; +var run = function () { + var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { + var backend_name, framework_name, output_table, total_elapsed_time, i, sample, start, out_vec, pred_label; + return regeneratorRuntime.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + if (runner) { + _context.next = 11; + break; + } + + backend_name = document.querySelector('input[name=backend_name]:checked').value; + framework_name = document.querySelector('input[name=framework_name]:checked').value; + _context.next = 5; + return WebDNN.load('./output_' + framework_name, { backendOrder: backend_name }); + + case 5: + runner = _context.sent; + + msg('Backend: ' + runner.backendName + ', model converted from ' + framework_name); + console.info('Backend: ' + runner.backendName + ', model converted from ' + framework_name); + _context.next = 10; + return fetchSamples('./output_' + framework_name + '/test_samples.json'); + + case 10: + test_samples = _context.sent; + + case 11: + output_table = document.getElementById('result'); + + resetOutputTable(output_table); + + total_elapsed_time = 0; + i = 0; + + case 15: + if (!(i < test_samples.length)) { + _context.next = 31; + break; + } + + sample = test_samples[i]; + + runner.getInputViews()[0].set(sample.x); + console.log('ground truth: ' + sample.y); + + start = performance.now(); + _context.next = 22; + return runner.run(); + + case 22: + total_elapsed_time += performance.now() - start; + + out_vec = runner.getOutputViews()[0].toActual(); + pred_label = WebDNN.Math.argmax(out_vec)[0]; + + console.log('predicted: ' + pred_label); + console.log(out_vec); + displayPrediction(output_table, sample.x, pred_label, sample.y); + + case 28: + i++; + _context.next = 15; + break; + + case 31: + console.log('Total Elapsed Time[ms/image]: ' + (total_elapsed_time / test_samples.length).toFixed(2)); + + case 32: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + return function run() { + return _ref.apply(this, arguments); + }; +}(); + +var fetchSamples = function () { + var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2(path) { + var response, json, samples, i; + return regeneratorRuntime.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return fetch(path); + + case 2: + response = _context2.sent; + + if (response.ok) { + _context2.next = 5; + break; + } + + throw new Error('Failed to load test samples'); + + case 5: + _context2.next = 7; + return response.json(); + + case 7: + json = _context2.sent; + samples = []; + + for (i = 0; i < json.length; i++) { + samples.push({ 'x': new Float32Array(json[i]['x']), 'y': json[i]['y'] }); + } + + return _context2.abrupt('return', samples); + + case 11: + case 'end': + return _context2.stop(); + } } - sample = test_samples[i]; - run_if.inputViews[0].set(sample.x); - console.log('ground truth: ' + sample.y); + }, _callee2, this); + })); - start = performance.now(); - run_if.run().then(function () { + return function fetchSamples(_x) { + return _ref2.apply(this, arguments); + }; +}(); - total_elapsed_time += performance.now() - start; +function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } - var out_vec = run_if.outputViews[0]; - var pred_label = WebDNN.Math.argmax(out_vec)[0]; - console.log('predicted: ' + pred_label); - console.log(out_vec); - displayPrediction(output_table, sample.x, pred_label, sample.y); +var runner = null; +var test_samples = null; - i++; - loop_code(); - }); - }; +function msg(s) { + document.getElementById('msg_place').textContent = s; +} - loop_code(); - }).catch(function (reason) { - console.error('Loading test samples failed: ' + reason); +function run_entry() { + run().then(function () { + console.log('run finished'); + }).catch(function (error) { + msg('run failed: ' + error); + console.error('run failed ' + error); }); - } -function fetchSamples(path) { - return fetch(path).then(function (response) { - return response.json(); - }).then(function (json) { - var samples = []; - for (var i = 0; i < json.length; i++) { - samples.push({ 'x': new Float32Array(json[i]['x']), 'y': json[i]['y'] }); - } - return samples; - }).catch(function (reason) { - return Promise.reject(reason); - }); +function reset_backend() { + runner = null; + resetOutputTable(document.getElementById('result')); + msg('Resetted backend'); } function resetOutputTable(table) { - let rows = table.children; - for (let i = rows.length - 1; i >= 1; i--) { + var rows = table.children; + for (var i = rows.length - 1; i >= 1; i--) { table.removeChild(rows[i]); } } function displayPrediction(table, input_image, prediction, ground_truth) { - let tr = document.createElement('tr'); - let canvas = document.createElement('canvas'); + var tr = document.createElement('tr'); + var canvas = document.createElement('canvas'); canvas.width = 28; canvas.height = 28; - let ctx = canvas.getContext('2d'); - let img = ctx.createImageData(28, 28); - for (let i = 0; i < 28 * 28; i++) { - let pixel = input_image[i] * 255; - img.data[i * 4 + 0] = pixel;//r - img.data[i * 4 + 1] = pixel;//g - img.data[i * 4 + 2] = pixel;//b - img.data[i * 4 + 3] = 255;//a + var ctx = canvas.getContext('2d'); + var img = ctx.createImageData(28, 28); + for (var i = 0; i < 28 * 28; i++) { + var pixel = input_image[i] * 255; + img.data[i * 4 + 0] = pixel; //r + img.data[i * 4 + 1] = pixel; //g + img.data[i * 4 + 2] = pixel; //b + img.data[i * 4 + 3] = 255; //a } ctx.putImageData(img, 0, 0); tr.appendChild(document.createElement('td')).appendChild(canvas); diff --git a/example/mnist/descriptor_run_mnist.html b/example/mnist/descriptor_run_mnist.html index 969f4a6f2..7ed5a9225 100644 --- a/example/mnist/descriptor_run_mnist.html +++ b/example/mnist/descriptor_run_mnist.html @@ -12,11 +12,16 @@ WebDNN mnist example - +
+ Backend: - +
+ Framework for model: + + +
@@ -24,4 +29,4 @@
- \ No newline at end of file + diff --git a/example/mnist/descriptor_run_mnist.js b/example/mnist/descriptor_run_mnist.js index 0323a54f7..c564a4b3d 100644 --- a/example/mnist/descriptor_run_mnist.js +++ b/example/mnist/descriptor_run_mnist.js @@ -1,6 +1,7 @@ 'use strict'; -let run_if = null; +let runner = null; +let test_samples = null; function msg(s) { document.getElementById('msg_place').textContent = s; @@ -16,46 +17,38 @@ function run_entry() { } function reset_backend() { - run_if = null; + runner = null; resetOutputTable(document.getElementById('result')); msg('Resetted backend'); } async function run() { - if (!run_if) { + if (!runner) { let backend_name = document.querySelector('input[name=backend_name]:checked').value; - run_if = await WebDNN.prepareAll('./output', { backendOrder: backend_name }); - msg(`Backend: ${run_if.backendName}`); - console.info(`Backend: ${run_if.backendName}`); + let framework_name = document.querySelector('input[name=framework_name]:checked').value; + runner = await WebDNN.load(`./output_${framework_name}`, { backendOrder: backend_name }); + msg(`Backend: ${runner.backendName}, model converted from ${framework_name}`); + console.info(`Backend: ${runner.backendName}, model converted from ${framework_name}`); + test_samples = await fetchSamples(`./output_${framework_name}/test_samples.json`); } - let test_samples = await fetchSamples('./output/test_samples.json'); let output_table = document.getElementById('result'); resetOutputTable(output_table); let total_elapsed_time = 0; for (let i = 0; i < test_samples.length; i++) { let sample = test_samples[i]; - run_if.inputViews[0].set(sample.x); + runner.getInputViews()[0].set(sample.x); console.log(`ground truth: ${sample.y}`); let start = performance.now(); - await run_if.run(); + await runner.run(); total_elapsed_time += performance.now() - start; - let out_vec = run_if.outputViews[0]; + let out_vec = runner.getOutputViews()[0].toActual(); + console.log(out_vec); let pred_label = WebDNN.Math.argmax(out_vec)[0]; - // equivalent to - /* let pred_label = 0; - let pred_score = -Infinity; - for (let j = 0; j < out_vec.length; j++) { - if (out_vec[j] > pred_score) { - pred_score = out_vec[j]; - pred_label = j; - } - }*/ console.log(`predicted: ${pred_label}`); - console.log(out_vec); displayPrediction(output_table, sample.x, pred_label, sample.y); } console.log(`Total Elapsed Time[ms/image]: ${(total_elapsed_time / test_samples.length).toFixed(2)}`); @@ -63,6 +56,9 @@ async function run() { async function fetchSamples(path) { let response = await fetch(path); + if (!response.ok) { + throw new Error('Failed to load test samples'); + } let json = await response.json(); let samples = []; for (let i = 0; i < json.length; i++) { diff --git a/example/mnist/train_mnist.py b/example/mnist/train_mnist_chainer.py old mode 100755 new mode 100644 similarity index 83% rename from example/mnist/train_mnist.py rename to example/mnist/train_mnist_chainer.py index 1d78b7500..e936c8ece --- a/example/mnist/train_mnist.py +++ b/example/mnist/train_mnist_chainer.py @@ -31,6 +31,7 @@ try: import matplotlib + matplotlib.use('Agg') except ImportError: pass @@ -46,14 +47,19 @@ from chainer import training from chainer.training import extensions -from webdnn.graph.converters.chainer import ChainerGraphConverter +from webdnn.frontend.chainer import ChainerConverter from webdnn.backend.interface.generator import generate_descriptor + # Network definition class MLP(chainer.Chain): + """ + Simple multi-layer perceptron + """ - def __init__(self, n_units, n_out): - super(MLP, self).__init__( + def __init__(self, n_out): + n_units = 100 + super().__init__( # the size of the inputs to each layer will be inferred l1=L.Linear(None, n_units), # n_in -> n_units l2=L.Linear(None, n_units), # n_units -> n_units @@ -61,13 +67,37 @@ def __init__(self, n_units, n_out): ) def __call__(self, x): - h1 = F.relu(self.l1(x)) + h1 = F.hard_sigmoid(self.l1(x)) h2 = F.relu(self.l2(h1)) return self.l3(h2) +class Conv(chainer.Chain): + """ + Simple multi-layer perceptron + """ + + def __init__(self, n_out): + super().__init__( + # the size of the inputs to each layer will be inferred + l1=L.Convolution2D(None, 8, ksize=3), # n_in -> n_units + l2=L.DilatedConvolution2D(None, 16, ksize=3, dilate=2), # n_units -> n_units + l3=L.Linear(None, n_out), # n_units -> n_out + ) + + def __call__(self, x): + h1 = F.sigmoid(self.l1(x)) + h2 = F.tanh(self.l2(h1)) + return self.l3(h2) + + +models = {"mlp": MLP, "conv": Conv} + + def main(): parser = argparse.ArgumentParser(description='Chainer example: MNIST') + parser.add_argument("--model", default="mlp", + choices=["mlp", "conv"]) parser.add_argument('--batchsize', '-b', type=int, default=100, help='Number of images in each mini-batch') parser.add_argument('--epoch', '-e', type=int, default=5, @@ -76,16 +106,13 @@ def main(): help='Frequency of taking a snapshot') parser.add_argument('--gpu', '-g', type=int, default=-1, help='GPU ID (negative value indicates CPU)') - parser.add_argument('--out', '-o', default='output', + parser.add_argument('--out', '-o', default='output_chainer', help='Directory to output the graph descriptor and sample test data') parser.add_argument('--resume', '-r', default='', help='Resume the training from snapshot') - parser.add_argument('--unit', '-u', type=int, default=100, - help='Number of units') args = parser.parse_args() print('GPU: {}'.format(args.gpu)) - print('# unit: {}'.format(args.unit)) print('# Minibatch-size: {}'.format(args.batchsize)) print('# epoch: {}'.format(args.epoch)) print('') @@ -95,7 +122,7 @@ def main(): # Set up a neural network to train # Classifier reports softmax cross entropy loss and accuracy at every # iteration, which will be used by the PrintReport extension below. - model = L.Classifier(MLP(args.unit, 10)) + model = L.Classifier(models[args.model](10)) if args.gpu >= 0: # Make a specified GPU current chainer.cuda.get_device_from_id(args.gpu).use() @@ -106,7 +133,7 @@ def main(): optimizer.setup(model) # Load the MNIST dataset - train, test = chainer.datasets.get_mnist() + train, test = chainer.datasets.get_mnist(ndim=3) train_iter = chainer.iterators.SerialIterator(train, args.batchsize) test_iter = chainer.iterators.SerialIterator(test, args.batchsize, @@ -165,8 +192,8 @@ def main(): example_input = numpy.expand_dims(train[0][0], axis=0) # example input (anything ok, (batch_size, 784)) x = chainer.Variable(example_input) - y = model.predictor(x) # run model (without softmax) - graph = ChainerGraphConverter().convert_from_inout_vars([x], [y]) # convert graph to intermediate representation + y = F.softmax(model.predictor(x)) # run model + graph = ChainerConverter().convert_from_inout_vars([x], [y]) # convert graph to intermediate representation for backend in ["webgpu", "webassembly", "fallback"]: try: exec_info = generate_descriptor(backend, graph) @@ -180,9 +207,10 @@ def main(): test_samples_json = [] for i in range(10): image, label = test[i] - test_samples_json.append({'x': image.tolist(), 'y': int(label)}) + test_samples_json.append({'x': image.flatten().tolist(), 'y': int(label)}) with open(os.path.join(args.out, 'test_samples.json'), 'w') as f: json.dump(test_samples_json, f) + if __name__ == '__main__': main() diff --git a/example/convert_keras/mnist_mlp.py b/example/mnist/train_mnist_keras.py similarity index 62% rename from example/convert_keras/mnist_mlp.py rename to example/mnist/train_mnist_keras.py index d54523f83..deee746cc 100644 --- a/example/convert_keras/mnist_mlp.py +++ b/example/mnist/train_mnist_keras.py @@ -9,17 +9,19 @@ import argparse import os import json +import subprocess import keras from keras.datasets import mnist from keras.models import Sequential, Model -from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D, Input, add, GlobalAveragePooling2D +from keras.layers import Dense, Dropout, Flatten, Conv2D, AtrousConv2D, MaxPooling2D, Input, add, \ + GlobalAveragePooling2D, Activation from keras.optimizers import RMSprop from keras import backend as K parser = argparse.ArgumentParser() -parser.add_argument("--model", default="fc", choices=["fc", "conv", "residual"]) -parser.add_argument("--out", default="output_mnist") +parser.add_argument("--model", default="fc", choices=["fc", "conv", "dilated_conv", "residual", "complex"]) +parser.add_argument("--out", default="output_keras") args = parser.parse_args() batch_size = 128 @@ -32,7 +34,7 @@ # input image dimensions img_rows, img_cols = 28, 28 -if args.model in ["conv", "residual"]: +if args.model in ["conv", "dilated_conv", "residual", "complex"]: if K.image_data_format() == "channels_first": raise NotImplementedError("Currently, WebDNN converter does not data_format==channels_first") # x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols) @@ -74,9 +76,22 @@ model.add(Dense(32, activation="relu")) model.add(Dropout(0.5)) model.add(Dense(num_classes, activation="softmax")) +elif args.model == "dilated_conv": + model = Sequential() + model.add(AtrousConv2D(8, kernel_size=(3, 3), atrous_rate=(2, 2), + activation="relu", + input_shape=input_shape)) # shape is 5x5 + model.add(AtrousConv2D(16, kernel_size=(3, 3), atrous_rate=(3, 3), + activation="relu")) # shape is 7x7 + model.add(MaxPooling2D(pool_size=(2, 2))) + model.add(Dropout(0.25)) + model.add(Flatten()) + model.add(Dense(32, activation="relu")) + model.add(Dropout(0.5)) + model.add(Dense(num_classes, activation="softmax")) elif args.model == "fc": model = Sequential() - model.add(Dense(512, activation="relu", input_shape=(784,))) + model.add(Dense(512, activation="hard_sigmoid", input_shape=(784,))) model.add(Dropout(0.2)) model.add(Dense(512, activation="relu")) model.add(Dropout(0.2)) @@ -95,6 +110,34 @@ nn_output = Dense(num_classes, activation="softmax")(hidden) model = Model(inputs=[nn_input], outputs=[nn_output]) +elif args.model == "complex": + # graph which has graph and sequential + # this is for testing converting complex model + nn_input = Input(shape=(28, 28, 1)) + + hidden_1 = Conv2D(8, kernel_size=(3, 3), activation="relu")(nn_input) + + submodel_input = Input(shape=(26, 26, 8)) + submodel_conv = Conv2D(8, kernel_size=(3, 3), activation="relu") + submodel_1 = submodel_conv(submodel_input) + submodel_2 = submodel_conv(submodel_1) # use same layer multiple times + submodel_3 = Conv2D(16, kernel_size=(3, 3), activation="relu")(submodel_1) + submodel = Model(inputs=[submodel_input], outputs=[submodel_3, submodel_2]) + + subseq = Sequential() + subseq.add(Conv2D(16, kernel_size=(3, 3), activation="relu", input_shape=(22, 22, 16))) + subseq.add(Flatten()) + subseq.add(Dense(10)) + + hidden_2, hidden_3 = submodel(hidden_1) + hidden_4 = subseq(hidden_2) + hidden_5 = Flatten()(hidden_3) + hidden_6 = Dense(10)(hidden_5) + hidden_sum = add([hidden_4, hidden_6]) + nn_output = Activation(activation="softmax")(hidden_sum) + + model = Model(inputs=[nn_input], outputs=[nn_output]) + else: raise NotImplementedError("Unknown model type") @@ -123,3 +166,14 @@ test_samples_json.append({"x": x_test[i].flatten().tolist(), "y": int(y_test_orig[i])}) with open(os.path.join(args.out, "test_samples.json"), "w") as f: json.dump(test_samples_json, f) + +print("Converting model into WebDNN format (graph descriptor)") +input_shape_with_batchsize = ('1',) + input_shape +# input_shape_with_batchsize = ('N',) + input_shape # placeholder +# only for demo purpose, maybe not safe +convert_keras_command = f"python ../../bin/convert_keras.py {args.out}/keras_model/mnist_mlp.h5 --input_shape '{input_shape_with_batchsize}' --out {args.out}" +print("$ " + convert_keras_command) + +subprocess.check_call(convert_keras_command, shell=True) + +print("Done.") diff --git a/example/mnist/train_mnist_pytorch.py b/example/mnist/train_mnist_pytorch.py new file mode 100644 index 000000000..bf17ea625 --- /dev/null +++ b/example/mnist/train_mnist_pytorch.py @@ -0,0 +1,120 @@ +import argparse +import json +import os +from os import path + +import torch +import torchvision +from torch import nn +from torch import optim +from torch.autograd import Variable as PTVariable +from torch.nn import functional as F + +from webdnn.backend.interface.generator import generate_descriptor +from webdnn.frontend.pytorch import PyTorchConverter + + +class Model(nn.Module): + def __init__(self, n_units, n_out): + super(Model, self).__init__() + self.l1 = nn.Linear(28 * 28, n_units) + self.l2 = nn.Linear(n_units, n_units) + self.l3 = nn.Linear(n_units, n_out) + + def forward(self, x: PTVariable): + + x = x.view(x.size(0), -1) + h1 = F.relu(self.l1(x)) + h2 = F.relu(self.l2(h1)) + return self.l3(h2) + + +def main(): + parser = argparse.ArgumentParser(description="PyTorch example: MNIST") + parser.add_argument("--batchsize", "-b", type=int, default=100, help="Number of images in each mini-batch") + parser.add_argument("--epoch", "-e", type=int, default=5, help="Number of sweeps over the dataset to train") + parser.add_argument("--gpu", "-g", action="store_true", help="If true, training is executed by GPU") + parser.add_argument("--out", "-o", default="output_pytorch", help="Directory to output the graph descriptor and sample test data") + parser.add_argument("--unit", "-u", type=int, default=100, help="Number of hidden units") + parser.add_argument("--data", "-d", default="./output_pytorch/dataset/mist", help="Root directory of dataset") + args = parser.parse_args() + + print("Setting:") + print(" GPU: {}".format(args.gpu)) + print(" # of unit: {}".format(args.unit)) + print(" # of Minibatch-size: {}".format(args.batchsize)) + print(" # of epoch: {}".format(args.epoch)) + print("") + + # setup + model = Model(args.unit, 10) + if args.gpu: + model.cuda() + + criterion = nn.CrossEntropyLoss() + optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) + trainset = torchvision.datasets.MNIST(root=args.data, train=True, download=True, transform=torchvision.transforms.ToTensor()) + trainloader = torch.utils.data.DataLoader(trainset, batch_size=args.batchsize, shuffle=True, num_workers=2) + testset = torchvision.datasets.MNIST(root=args.data, train=False, download=True, transform=torchvision.transforms.ToTensor()) + testloader = torch.utils.data.DataLoader(testset, batch_size=1, shuffle=False, num_workers=2) + + # training + print("Training:") + for epoch in range(args.epoch): + running_loss = 0.0 + for i, data in enumerate(trainloader, 0): + inputs, labels = data + inputs, labels = PTVariable(inputs), PTVariable(labels) + + optimizer.zero_grad() + + loss = criterion(model(inputs), labels) + loss.backward() + optimizer.step() + + running_loss += loss.data[0] * inputs.size()[0] + + print(f" epoch:{epoch+1:d} loss:{running_loss / len(trainset):.3f}") + print(" Training finished") + print("") + + # save model state + print("Save trained model state:") + os.makedirs(path.join(args.out, "torch_model"), exist_ok=True) + filename = path.join(args.out, "torch_model/checkpoint.pth.tar") + torch.save({'state_dict': model.state_dict(), 'optimizer': optimizer.state_dict()}, filename) + print(f" {filename}") + print("") + + # convert + print("Convert model into WebDNN GraphDescriptor") + converter = PyTorchConverter() + x = PTVariable(torch.FloatTensor(1, 1, 28, 28).zero_()) # dummy data + y = model(x) + graph = converter.convert([x], [y]) + for backend in ["webgpu", "webassembly", "fallback"]: + # noinspection PyBroadException + try: + exec_info = generate_descriptor(backend, graph) + exec_info.save(args.out) + except Exception as ex: + print(f"Failed generating descriptor for backend {backend}: {str(ex)}\n") + else: + print(f"Backend {backend} ok\n") + + # prepare test samples + print('Exporting test samples (for demo purpose)') + test_samples_json = [] + test_iter = testloader.__iter__() + for i in range(10): + image, label = test_iter.next() + test_samples_json.append({ + 'x': image.numpy().flatten().tolist(), + 'y': label[0] + }) + with open(os.path.join(args.out, 'test_samples.json'), 'w') as f: + json.dump(test_samples_json, f) + + +if __name__ == "__main__": + main() diff --git a/example/convert_resnet/README.md b/example/resnet/README.md similarity index 54% rename from example/convert_resnet/README.md rename to example/resnet/README.md index be6cee356..ab5be1758 100644 --- a/example/convert_resnet/README.md +++ b/example/resnet/README.md @@ -1,8 +1,29 @@ # ResNet-50 model conversion example -This example converts pre-trained ResNet-50 image classification model of Chainer into WebDNN format (graph descriptor). +This example converts pre-trained ResNet-50 image classification model of Keras or Chainer into WebDNN format (graph descriptor). -## Model conversion -You can convert the model with `convert.py`. +## Model conversion (Keras) +Our scripts assume Keras version 2.0.x. + +`convert_resnet_keras.py` does all things at once. + +The script consists of two parts: + +1. Exporting Keras sample model + In python, run the following statements. + + ```python + from keras.applications import resnet50 + model = resnet50.ResNet50(include_top=True, weights='imagenet') + model.save("resnet50.h5") + ``` + +2. Convert model into WebDNN graph descriptor + ```sh + python ../../bin/convert_keras.py resnet50.h5 --input_shape '(1,224,224,3)' --out output_keras + ``` + +## Model conversion (Chainer) +You can convert the model with `convert_resnet_chainer.py`. ``` python convert.py --model resnet50 --backend webgpu --encoding eightbit diff --git a/example/convert_resnet/convert.py b/example/resnet/convert_resnet_chainer.py similarity index 57% rename from example/convert_resnet/convert.py rename to example/resnet/convert_resnet_chainer.py index 963d8350d..a925b9c5c 100644 --- a/example/convert_resnet/convert.py +++ b/example/resnet/convert_resnet_chainer.py @@ -3,18 +3,16 @@ """ import argparse -import sys import os -from os import path +import sys import chainer import chainer.computational_graph import numpy as np from webdnn.backend.interface.generator import generate_descriptor -from webdnn.graph.converters.chainer import ChainerGraphConverter - -OUTPUT_DIR = path.join(path.dirname(__file__), "./output") +from webdnn.frontend.chainer import ChainerConverter +from webdnn.util import console def main(): @@ -22,11 +20,16 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument("--model", default="resnet50", choices=["vgg16", "resnet50"]) - parser.add_argument("--backend", default="webgpu", choices=["webgpu", "webassembly", "fallback"]) + parser.add_argument("--backend", default="webgpu,webassembly,fallback") parser.add_argument("--encoding") + parser.add_argument('--out', '-o', default='output_chainer', + help='Directory to output the graph descriptor') + args = parser.parse_args() - sample_image = np.zeros((224, 224, 3), dtype=np.uint8)#PIL.Image.open("") + os.makedirs(args.out, exist_ok=True) + + sample_image = np.zeros((224, 224, 3), dtype=np.uint8) # PIL.Image.open("") if args.model == "vgg16": link = chainer.links.model.vision.vgg.VGG16Layers() prepared_image = chainer.links.model.vision.vgg.prepare(sample_image) # BGR, CHW @@ -40,14 +43,23 @@ def main(): nn_input = chainer.Variable(np.array([prepared_image], dtype=np.float32)) nn_output = link(nn_input, layers=[out_layer_name])[out_layer_name] # 'prob' is also possible (uses softmax) chainer_cg = chainer.computational_graph.build_computational_graph([nn_output]) - converter = ChainerGraphConverter() + converter = ChainerConverter() graph = converter.convert(chainer_cg, [nn_input], [nn_output]) # type: Graph - graph_exec_data = generate_descriptor(args.backend, graph, constant_encoder_name=args.encoding) + any_backend_failed = False + last_backend_exception = None + for backend in args.backend.split(","): + try: + graph_exec_data = generate_descriptor(backend, graph, constant_encoder_name=args.encoding) + graph_exec_data.save(args.out) + except Exception as ex: + any_backend_failed = True + last_backend_exception = ex + console.error(f"Failed generating descriptor for backend {backend}: {str(ex)}\n") - os.makedirs(OUTPUT_DIR, exist_ok=True) + if any_backend_failed: + raise last_backend_exception - graph_exec_data.save(OUTPUT_DIR) if __name__ == "__main__": main() diff --git a/example/resnet/convert_resnet_keras.py b/example/resnet/convert_resnet_keras.py new file mode 100644 index 000000000..4e08e2f89 --- /dev/null +++ b/example/resnet/convert_resnet_keras.py @@ -0,0 +1,44 @@ +""" +Example of converting ResNet-50 Keras model +""" + +import argparse +import os +import subprocess +from os import path + +from keras.applications import resnet50 + +from webdnn.util import console + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--model", default="resnet50", choices=["resnet50"]) + parser.add_argument('--out', '-o', default='output_keras', + help='Directory to output the graph descriptor') + parser.add_argument("--encoding", help="name of weight encoder") + + args = parser.parse_args() + keras_model_path = path.join(args.out, "resnet50.h5") + + if not os.path.exists(keras_model_path): + console.stderr("Exporting Keras model into file") + os.makedirs(args.out, exist_ok=True) + model = resnet50.ResNet50(include_top=True, weights='imagenet') + model.save(keras_model_path) + + console.stderr("Converting model into WebDNN format (graph descriptor)") + # only for demo purpose, maybe not safe + convert_keras_command = f"python ../../bin/convert_keras.py {keras_model_path} --input_shape '(1,224,224,3)' --out {args.out}" + if args.encoding: + convert_keras_command += f" --encoding {args.encoding}" + + console.stderr("$ " + convert_keras_command) + subprocess.check_call(convert_keras_command, shell=True) + + console.stderr("Done.") + + +if __name__ == "__main__": + main() diff --git a/example/convert_resnet/descriptor_run_resnet.es5.html b/example/resnet/descriptor_run_resnet.es5.html similarity index 86% rename from example/convert_resnet/descriptor_run_resnet.es5.html rename to example/resnet/descriptor_run_resnet.es5.html index 96e61ec52..212d4a40f 100644 --- a/example/convert_resnet/descriptor_run_resnet.es5.html +++ b/example/resnet/descriptor_run_resnet.es5.html @@ -22,11 +22,15 @@

For EcmaScript5 environment such as IE11



Using canvas's image slightly changes the output due to interpolation.
- +
+ Backend:
+ Framework for model: + +
diff --git a/example/convert_resnet/descriptor_run_resnet.es5.js b/example/resnet/descriptor_run_resnet.es5.js similarity index 83% rename from example/convert_resnet/descriptor_run_resnet.es5.js rename to example/resnet/descriptor_run_resnet.es5.js index 5ac5cb574..6df951f90 100644 --- a/example/convert_resnet/descriptor_run_resnet.es5.js +++ b/example/resnet/descriptor_run_resnet.es5.js @@ -2,38 +2,40 @@ var prepare_run = function () { var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { - var backend_name, run_if; + var backend_name, framework_name, backend_key, runner; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: backend_name = document.querySelector('input[name=backend_name]:checked').value; + framework_name = document.querySelector('input[name=framework_name]:checked').value; + backend_key = backend_name + framework_name; - if (backend_name in run_ifs) { - _context.next = 10; + if (backend_key in runners) { + _context.next = 12; break; } log('Initializing and loading model'); - _context.next = 5; - return WebDNN.prepareAll('./output', { backendOrder: backend_name }); + _context.next = 7; + return WebDNN.load('./output_' + framework_name, { backendOrder: backend_name }); - case 5: - run_if = _context.sent; + case 7: + runner = _context.sent; - log('Loaded backend: ' + run_if.backendName); + log('Loaded backend: ' + runner.backendName + ', model converted from ' + framework_name); - run_ifs[backend_name] = run_if; - _context.next = 11; + runners[backend_key] = runner; + _context.next = 13; break; - case 10: + case 12: log('Model is already loaded'); - case 11: - return _context.abrupt('return', run_ifs[backend_name]); + case 13: + return _context.abrupt('return', runners[backend_key]); - case 12: + case 14: case 'end': return _context.stop(); } @@ -48,7 +50,7 @@ var prepare_run = function () { var run = function () { var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2() { - var run_if, test_image, test_samples, total_elapsed_time, pred_label, i, sample, start, out_vec, top_labels, predicted_str, j; + var runner, test_image, test_samples, total_elapsed_time, pred_label, i, sample, start, out_vec, top_labels, predicted_str, j; return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { @@ -57,7 +59,7 @@ var run = function () { return prepare_run(); case 2: - run_if = _context2.sent; + runner = _context2.sent; test_image = getImageData(); test_samples = [test_image]; total_elapsed_time = 0; @@ -72,16 +74,16 @@ var run = function () { sample = test_samples[i]; - run_if.inputViews[0].set(sample); + runner.getInputViews()[0].set(sample); start = performance.now(); _context2.next = 14; - return run_if.run(); + return runner.run(); case 14: total_elapsed_time += performance.now() - start; - out_vec = run_if.outputViews[0]; + out_vec = runner.getOutputViews()[0].toActual(); top_labels = WebDNN.Math.argmax(out_vec, 5); predicted_str = 'Predicted:'; @@ -179,7 +181,7 @@ function load_image() { } var test_samples = void 0; -var run_ifs = {}; +var runners = {}; function makeMatFromJson(mat_data) { var mat = new Float32Array(mat_data['data']); diff --git a/example/convert_keras/descriptor_run_resnet.html b/example/resnet/descriptor_run_resnet.html similarity index 81% rename from example/convert_keras/descriptor_run_resnet.html rename to example/resnet/descriptor_run_resnet.html index 574739bcc..4b0974988 100644 --- a/example/convert_keras/descriptor_run_resnet.html +++ b/example/resnet/descriptor_run_resnet.html @@ -14,15 +14,19 @@

ResNet-50 conversion WebDNN example

You have to convert ResNet-50 with python script before running this webpage.
- Image URL:
+ Image URL:


Using canvas's image slightly changes the output due to interpolation.
- +
+ Backend:
+ Framework for model: + +
diff --git a/example/convert_keras/descriptor_run_resnet.js b/example/resnet/descriptor_run_resnet.js similarity index 79% rename from example/convert_keras/descriptor_run_resnet.js rename to example/resnet/descriptor_run_resnet.js index 8874a70ef..52ae32678 100644 --- a/example/convert_keras/descriptor_run_resnet.js +++ b/example/resnet/descriptor_run_resnet.js @@ -18,7 +18,7 @@ function log(msg) { function load_image() { var img = new Image(); - img.onload = function () { + img.onload = function() { var ctx = document.getElementById('input_image').getContext('2d'); // shrink instead of crop ctx.drawImage(img, 0, 0, 224, 224); @@ -26,31 +26,33 @@ function load_image() { document.getElementById('run_button').disabled = false; log('Image loaded to canvas'); } - img.onerror = function () { + img.onerror = function() { log('Failed to load image'); } img.src = document.querySelector("input[name=image_url]").value; } let test_samples; -let run_ifs = {}; +let runners = {}; async function prepare_run() { let backend_name = document.querySelector('input[name=backend_name]:checked').value; - if (!(backend_name in run_ifs)) { + let framework_name = document.querySelector('input[name=framework_name]:checked').value; + let backend_key = backend_name + framework_name; + if (!(backend_key in runners)) { log('Initializing and loading model'); - let run_if = await WebDNN.prepareAll('./output', { backendOrder: backend_name }); - log(`Loaded backend: ${run_if.backendName}`); + let runner = await WebDNN.load(`./output_${framework_name}`, {backendOrder: backend_name}); + log(`Loaded backend: ${runner.backendName}, model converted from ${framework_name}`); - run_ifs[backend_name] = run_if; + runners[backend_key] = runner; } else { log('Model is already loaded'); } - return run_ifs[backend_name]; + return runners[backend_key]; } async function run() { - let run_if = await prepare_run(); + let runner = await prepare_run(); let test_image = getImageData(); let test_samples = [test_image]; @@ -59,13 +61,13 @@ async function run() { let pred_label; for (let i = 0; i < test_samples.length; i++) { let sample = test_samples[i]; - run_if.inputViews[0].set(sample); + runner.getInputViews()[0].set(sample); let start = performance.now(); - await run_if.run(); + await runner.run(); total_elapsed_time += performance.now() - start; - let out_vec = run_if.outputViews[0]; + let out_vec = runner.getOutputViews()[0].toActual(); let top_labels = WebDNN.Math.argmax(out_vec, 5); let predicted_str = 'Predicted:'; for (let j = 0; j < top_labels.length; j++) { diff --git a/example/text_generation/convert_model.sh b/example/text_generation/convert_model.sh new file mode 100755 index 000000000..1d3cb124a --- /dev/null +++ b/example/text_generation/convert_model.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +mkdir -p output +wget https://github.com/mil-tokyo/webdnn-data/raw/master/models/lstm_text_generation/lstm_text_generation.h5 -O output/lstm_text_generation.h5 +wget https://github.com/mil-tokyo/webdnn-data/raw/master/models/lstm_text_generation/model_setting.json -O output/model_setting.json +python ../../bin/convert_keras.py output/lstm_text_generation.h5 --input_shape '(1,40,57)' --out output --backend webgpu,webassembly diff --git a/example/text_generation/descriptor_run_text_generation.html b/example/text_generation/descriptor_run_text_generation.html new file mode 100644 index 000000000..7a42a6e35 --- /dev/null +++ b/example/text_generation/descriptor_run_text_generation.html @@ -0,0 +1,37 @@ + + + + + Text generation WebDNN example + + + + + + + +

Text generation WebDNN example

+Generates text like Nietzsche after the seed text.
+You have to convert model with script before running this webpage. +
+ Backend: + + +
+ Framework for model: +
+ Seed text: + + + +
+ Generated text:
+
+ +
+
+
+ + + diff --git a/example/text_generation/descriptor_run_text_generation.js b/example/text_generation/descriptor_run_text_generation.js new file mode 100644 index 000000000..5f99746e3 --- /dev/null +++ b/example/text_generation/descriptor_run_text_generation.js @@ -0,0 +1,115 @@ +'use strict'; + +var metadata = null; + +function run_entry() { + run().then(() => { + log('Run finished'); + }).catch((error) => { + log('Error: ' + error); + }); +} + +function run_change_seed() { + let n_sent = metadata.example_sentences.length; + document.querySelector('input[name=seed_text]').value = metadata.example_sentences[Math.floor(Math.random() * (n_sent + 1))]; +} + +function log(msg) { + let msg_node = document.getElementById('messages'); + msg_node.appendChild(document.createElement('br')); + msg_node.appendChild(document.createTextNode(msg)); +} + +let runners = {}; + +async function prepare_run() { + let backend_name = document.querySelector('input[name=backend_name]:checked').value; + let backend_key = backend_name; + if (!(backend_key in runners)) { + log('Initializing and loading model'); + let runner = await WebDNN.load(`./output`, { backendOrder: backend_name }); + log(`Loaded backend: ${runner.backendName}`); + + runners[backend_key] = runner; + } else { + log('Model is already loaded'); + } + return runners[backend_key]; +} + +function sentence_to_array(sentence) { + let maxlen = metadata.maxlen; + let n_chars = metadata.n_chars; + let array = new Float32Array(1 * maxlen * n_chars);//NTC order + for (let i = 0; i < maxlen; i++) { + let char = sentence[sentence.length - maxlen + i]; + let char_idx = metadata.char_indices[char]; + if (char_idx === void 0) { + char_idx = 0; + } + array[i * n_chars + char_idx] = 1.0; + } + + return array; +} + +function sample_next_char(scores, temperature) { + let probs = new Float32Array(metadata.n_chars); + let prob_sum = 0.0; + for (let i = 0; i < metadata.n_chars; i++) { + let prob = Math.exp(Math.log(scores[i]) / temperature); + prob_sum += prob; + probs[i] = prob; + } + + let char_idx = metadata.n_chars - 1; + let rand = Math.random() * prob_sum; + for (let i = 0; i < metadata.n_chars; i++) { + rand -= probs[i]; + if (rand < 0.0) { + char_idx = i; + break; + } + } + + return metadata.indices_char['' + char_idx]; +} + +async function run() { + let runner = await prepare_run(); + + let sentence_seed = document.querySelector('#seed_text').textContent; + let sentence = sentence_seed; + + for (let i = 0; i < 100; i++) { + // input current sentence to the model + runner.getInputViews()[0].set(sentence_to_array(sentence)); + + // predict next character's probability + await runner.run(); + let out_vec = runner.getOutputViews()[0].toActual(); + // sample next character + let next_char = sample_next_char(out_vec, 1.0); + sentence += next_char; + console.log('output vector: ', out_vec); + } + document.getElementById('result_seed').textContent = sentence_seed; + document.getElementById('result_generated').textContent = sentence.slice(sentence_seed.length); +} + +document.addEventListener('DOMContentLoaded', async function (event) { + try { + let response = await fetch('output/model_setting.json'); + if (!response.ok) { + throw new Error('Metadata HTTP response is not OK'); + } + let json = await response.json(); + metadata = json; + document.querySelector('#seed_text').textContent = metadata['example_sentences'][0]; + document.getElementById('run_button').disabled = false; + document.getElementById('change_seed').disabled = false; + } catch (error) { + log('Failed to load metadata: ' + error); + } +}); diff --git a/example/text_generation/train_lstm_text_generation.py b/example/text_generation/train_lstm_text_generation.py new file mode 100644 index 000000000..f58384b13 --- /dev/null +++ b/example/text_generation/train_lstm_text_generation.py @@ -0,0 +1,131 @@ +''' +This is based on Keras's example. Feature of saving model and setting is added. +https://raw.githubusercontent.com/fchollet/keras/master/examples/lstm_text_generation.py + +Trained model can be obtained from +https://github.com/mil-tokyo/webdnn-data/raw/master/models/lstm_text_generation/lstm_text_generation.h5 + +Example script to generate text from Nietzsche's writings. + +At least 20 epochs are required before the generated text +starts sounding coherent. + +It is recommended to run this script on GPU, as recurrent +networks are quite computationally intensive. + +If you try this script on new data, make sure your corpus +has at least ~100k characters. ~1M is better. +''' + +from __future__ import print_function +from keras.models import Sequential +from keras.layers import Dense, Activation +from keras.layers import LSTM +from keras.optimizers import RMSprop +from keras.utils.data_utils import get_file +import numpy as np +import random +import sys +import os +import json +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("--out", default="output") +args = parser.parse_args() + +path = get_file('nietzsche.txt', origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt') +text = open(path).read().lower() +print('corpus length:', len(text)) + +chars = sorted(list(set(text))) +print('total chars:', len(chars)) +char_indices = dict((c, i) for i, c in enumerate(chars)) +indices_char = dict((i, c) for i, c in enumerate(chars)) + +# cut the text in semi-redundant sequences of maxlen characters +maxlen = 40 +step = 3 +sentences = [] +next_chars = [] +for i in range(0, len(text) - maxlen, step): + sentences.append(text[i: i + maxlen]) + next_chars.append(text[i + maxlen]) +print('nb sequences:', len(sentences)) + +print('Vectorization...') +X = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool) +y = np.zeros((len(sentences), len(chars)), dtype=np.bool) +for i, sentence in enumerate(sentences): + for t, char in enumerate(sentence): + X[i, t, char_indices[char]] = 1 + y[i, char_indices[next_chars[i]]] = 1 + +# saves char-index mapping +os.makedirs(args.out, exist_ok=True) +with open(os.path.join(args.out, "model_setting.json"), "w") as f: + json.dump({"char_indices": char_indices, + "indices_char": indices_char, + "maxlen": maxlen, + "n_chars": len(chars), + "example_sentences": random.sample(sentences, 100)}, f) + +# build the model: a single LSTM +print('Build model...') +model = Sequential() +model.add(LSTM(128, input_shape=(maxlen, len(chars)))) +model.add(Dense(len(chars))) +model.add(Activation('softmax')) + +optimizer = RMSprop(lr=0.01) +model.compile(loss='categorical_crossentropy', optimizer=optimizer) + + +def sample(preds, temperature=1.0): + # helper function to sample an index from a probability array + preds = np.asarray(preds).astype('float64') + preds = np.log(preds) / temperature + exp_preds = np.exp(preds) + preds = exp_preds / np.sum(exp_preds) + probas = np.random.multinomial(1, preds, 1) + return np.argmax(probas) + + +# train the model, output generated text after each iteration +for iteration in range(1, 60): + print() + print('-' * 50) + print('Iteration', iteration) + model.fit(X, y, + batch_size=128, + epochs=1) + + model.save(os.path.join(args.out, "lstm_text_generation.h5")) + + start_index = random.randint(0, len(text) - maxlen - 1) + + for diversity in [0.2, 0.5, 1.0, 1.2]: + print() + print('----- diversity:', diversity) + + generated = '' + sentence = text[start_index: start_index + maxlen] + generated += sentence + print('----- Generating with seed: "' + sentence + '"') + sys.stdout.write(generated) + + for i in range(400): + x = np.zeros((1, maxlen, len(chars))) + for t, char in enumerate(sentence): + x[0, t, char_indices[char]] = 1. + + preds = model.predict(x, verbose=0)[0] + next_index = sample(preds, diversity) + next_char = indices_char[next_index] + + generated += next_char + sentence = sentence[1:] + next_char + + sys.stdout.write(next_char) + sys.stdout.flush() + print() diff --git a/package.json b/package.json index e9c1c07b9..b5b21fbd1 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,42 @@ { - "name": "mil-web-dnn", - "version": "1.0.0", - "description": "Deep Neural Network Execution Framework for Web Browsers", - "main": "index.js", - "directories": { - "doc": "docs", - "example": "example", - "test": "test" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/mil-tokyo/mil-web-dnn.git" - }, - "author": [{ - "name": "Masatoshi Hidaka", - "email": "hidaka@mi.t.u-tokyo.ac.jp", - "url": "https://milhidaka.github.io/" - }, { - "name": "Yuichiro Kikura", - "email": "kikura@mi.t.u-tokyo.ac.jp", - "url": "https://github.com/Kiikurage" - }], - "license": "MIT", - "bugs": { - "url": "https://github.com/mil-tokyo/mil-web-dnn/issues" - }, - "homepage": "https://github.com/mil-tokyo/mil-web-dnn#readme", - "devDependencies": { - "typedoc": "^0.6.0" - } + "name": "WebDNN", + "version": "1.1.0", + "description": "Deep Neural Network Execution Framework for Web Browsers", + "main": "dist/webdnn.js", + "directories": { + "doc": "docs", + "example": "example", + "test": "test" + }, + "scripts": { + "test": "nosetests", + "test-kernel": "echo '\"npm run test-kernel\" is removed. please use \"npm run test-runtime\"'", + "test-runtime": "OPTIMIZE=0 nosetests -w ./test/runtime", + "build": "tsc -p ./src/descriptor_runner/tsconfig.json; tsc -p ./src/descriptor_runner/tsconfig.es5.json;", + "build-doc": "cd docs; make html" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/mil-tokyo/webdnn.git" + }, + "author": [ + { + "name": "Masatoshi Hidaka", + "email": "hidaka@mi.t.u-tokyo.ac.jp", + "url": "https://milhidaka.github.io/" + }, + { + "name": "Yuichiro Kikura", + "email": "kikura@mi.t.u-tokyo.ac.jp", + "url": "https://github.com/Kiikurage" + } + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/mil-tokyo/webdnn/issues" + }, + "homepage": "https://mil-tokyo.github.io/webdnn/", + "devDependencies": { + "typedoc": "^0.6.0" + } } diff --git a/setup.py b/setup.py index b779a726e..c2e8e6862 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="webdnn", - version="1.0.0", + version="1.1.0", package_dir={"": "src/graph_transpiler"}, packages=find_packages("src/graph_transpiler"), package_data={"": "*.js"} diff --git a/src/descriptor_runner/decoder/weight_decoder.ts b/src/descriptor_runner/decoder/weight_decoder.ts index 8436b4b43..1818a3524 100644 --- a/src/descriptor_runner/decoder/weight_decoder.ts +++ b/src/descriptor_runner/decoder/weight_decoder.ts @@ -1,12 +1,7 @@ -/// +/// namespace WebDNN { export interface WeightDecoder { - decode(data: Uint8Array, weight_allocation: WeightAllocation): Promise; - } - - export interface WeightAllocation { - total_size: number; - allocation: { [index: string]: { name: string, offset: number, size: number } } + decode(data: Uint8Array, memory_layout: MemoryLayout): Promise; } } diff --git a/src/descriptor_runner/decoder/weight_decoder_eightbit.ts b/src/descriptor_runner/decoder/weight_decoder_eightbit.ts index e688c0cab..b3627c592 100644 --- a/src/descriptor_runner/decoder/weight_decoder_eightbit.ts +++ b/src/descriptor_runner/decoder/weight_decoder_eightbit.ts @@ -27,8 +27,8 @@ namespace WebDNN { 9.054686427e-01, 9.226561785e-01, 9.398436546e-01, 9.570311308e-01, 9.742186666e-01, 9.914061427e-01, 1.0, ]; - async decode(data: Uint8Array, weight_allocation: WeightAllocation): Promise { - let dst = new Float32Array(weight_allocation.total_size); + async decode(data: Uint8Array, memory_layout: MemoryLayout): Promise { + let dst = new Float32Array(memory_layout.static.size); let data_view = new DataView(data.buffer, data.byteOffset); let src_offset = 0; while (src_offset < data.length) { diff --git a/src/descriptor_runner/decoder/weight_decoder_raw.ts b/src/descriptor_runner/decoder/weight_decoder_raw.ts index 16ce88f99..866c7df22 100644 --- a/src/descriptor_runner/decoder/weight_decoder_raw.ts +++ b/src/descriptor_runner/decoder/weight_decoder_raw.ts @@ -2,7 +2,7 @@ namespace WebDNN { export class WeightDecoderRaw implements WeightDecoder { - async decode(data: Uint8Array, weight_allocation: WeightAllocation) : Promise { + async decode(data: Uint8Array, memory_layout: MemoryLayout): Promise { return new Float32Array(data.buffer, data.byteOffset, data.byteLength / 4); } } diff --git a/src/descriptor_runner/descriptor_runner/descriptor_runner.ts b/src/descriptor_runner/descriptor_runner/descriptor_runner.ts index 7c20b8bf4..9663882a0 100644 --- a/src/descriptor_runner/descriptor_runner/descriptor_runner.ts +++ b/src/descriptor_runner/descriptor_runner/descriptor_runner.ts @@ -1,11 +1,45 @@ /// +/// +/// namespace WebDNN { /** * `DescriptorRunner` executes computation based on `GraphDescriptor`. + * + * Typically, DescriptorRunner takes 3 steps to execute DNN model. + * + * 1. Initialize static configurations + * + * Initialize things independent from runtime configuration. + * + * - `init()` + * - `load()` + * + * 2. Initialize dynamic configurations + * + * Initialize things depend on runtime configuration such as batch size, input image size, etc. + * + * - `setPlaceholderValue()` + * - `getInputViews()` + * - `getOutputViews()` + * + * 3. Execute the model + * + * - `run()` + * + * You need to do step 1 and 2 only once. We recommend to call `WebDNN.prepareAll()` instead + * to call `GraphDescriptor#load()` directly. In that method, all procedures in step 1 and 2 are performed. */ - export interface DescriptorRunner { - backend: string; + export abstract class DescriptorRunner { + readonly backendName: string; + descriptor: D | null = null; + placeholderContext: PlaceholderContext | null; + ignoreCache: boolean = false; + + /** + * Initialize this runner + */ + abstract async init(): Promise; /** * Fetch descriptor from specified directory. @@ -20,38 +54,26 @@ namespace WebDNN { * * @param progressCallback callback which is called to notice the loading is progressing. */ - load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; + abstract async load(directory: string, progressCallback?: (loaded: number, total: number) => any): Promise; /** - * set descriptor. - * @param descriptor descriptor which will be executed. + * Set actual value into placeholders. If no placeholder is exist in graph descriptor, it's no need to call this function. */ - setDescriptor(descriptor: GraphDescriptor): void; + abstract async setPlaceholderValue(values: { [key: string]: number }): Promise; /** - * compile kernels. + * Get input ArrayBufferView object */ - compile(): Promise; + abstract getInputViews(): SymbolicFloat32Array[]; /** - * load weight data - * @param weightsData weights data + * Get output ArrayBufferView object */ - loadWeights(weightsData: Uint8Array): Promise; + abstract getOutputViews(): SymbolicFloat32Array[]; /** * Run descriptor. You must call [[getInputViews]] and [[getOutputViews]] before calling this function. */ - run(): Promise; - - /** - * Get input ArrayBufferView object - */ - getInputViews(): Promise; - - /** - * Get output ArrayBufferView object - */ - getOutputViews(): Promise; + abstract async run(): Promise; } } diff --git a/src/descriptor_runner/descriptor_runner/descriptor_runner_fallback.ts b/src/descriptor_runner/descriptor_runner/descriptor_runner_fallback.ts index 9f78b0747..ba1e9e29c 100644 --- a/src/descriptor_runner/descriptor_runner/descriptor_runner_fallback.ts +++ b/src/descriptor_runner/descriptor_runner/descriptor_runner_fallback.ts @@ -1,123 +1,213 @@ /// /// +function wait(duration: number = 10) { + // let console.log to be displayed, and prevent freeze + return new Promise(resolve => setTimeout(resolve, duration)); +} + namespace WebDNN { - export class DescriptorRunnerFallback implements DescriptorRunner { - public descriptor: GraphDescriptorFallback; - kernelObj: any; - rawWeightArray: Float32Array; - weightArrays: Map; - variableArrays: Map; - public ignoreCache: boolean = false; - public backend: string = 'fallback'; - private inputViews: Float32Array[]; - private outputViews: Float32Array[]; - - constructor() { + export class DescriptorRunnerFallback extends DescriptorRunner { + readonly backendName = 'fallback'; + + private kernelObj: any; + private variableMap: Map | null; + private inputViews: SymbolicFloat32Array[] | null; + private outputViews: SymbolicFloat32Array[] | null; + private staticBuffer: Float32Array | null; + private dynamicBuffer: Float32Array | null; + + constructor(option?: any) { + super(); + } + + async init(): Promise { + //nothing to do } async load(directory: string, progressCallback?: (loaded: number, total: number) => any) { - let graph_url = `${directory}/graph_${this.backend}.json`; - if (this.ignoreCache) { - graph_url += '?t=' + Date.now(); - } - graph_url = transformUrl(graph_url); - let graph_fetch = await WebDNN.fetch(graph_url); - if (!graph_fetch.ok) { - throw new Error(`${graph_url} cannot be loaded`); - } - this.descriptor = await graph_fetch.json(); - await this.compile(); + let [descriptor, weightRawArray] = await Promise.all([ + WebDNN.fetch(`${directory}/graph_${this.backendName}.json`, {ignoreCache: this.ignoreCache}) + .then(res => res.json() as Promise), - let weight_url = `${directory}/weight_${this.backend}.bin`; - if (this.ignoreCache) { - weight_url += '?t=' + Date.now(); - } - weight_url = transformUrl(weight_url); - let weights_data_ab = await readArrayBufferProgressively(await WebDNN.fetch(weight_url), progressCallback); - await this.loadWeights(new Uint8Array(weights_data_ab)); - } + WebDNN.fetch(`${directory}/weight_${this.backendName}.bin`, {ignoreCache: this.ignoreCache}) + .then(res => readArrayBufferProgressively(res, progressCallback)) + ]); - setDescriptor(descriptor: GraphDescriptorFallback) { - this.descriptor = descriptor; + this.setDescriptor(descriptor); + + await this.compile(); + await this.initializeStaticBuffer(weightRawArray); + if (this.placeholderContext && this.placeholderContext.isResolved) await this.initializeDynamicBuffer(); } - async compile(): Promise { - this.compileKernel(); - this.rawWeightArray = new Float32Array(this.descriptor.weight_allocation.total_size); - let weight_name_alloc = this.descriptor.weight_allocation.allocation; - this.weightArrays = new Map(); - for (let name in weight_name_alloc) { - let alloc = weight_name_alloc[name]; - this.weightArrays.set(name, new Float32Array(this.rawWeightArray.buffer, alloc.offset * Float32Array.BYTES_PER_ELEMENT, alloc.size)); - } + private setDescriptor(descriptor: GraphDescriptorFallback) { + this.descriptor = descriptor; - this.variableArrays = new Map(); - let variable_name_alloc = this.descriptor.variable_allocation.allocation; - for (let name in variable_name_alloc) { - let alloc = variable_name_alloc[name]; - this.variableArrays.set(name, new Float32Array(alloc.size)); - } + // reset + this.placeholderContext = new PlaceholderContext(); + this.placeholderContext.update(descriptor.placeholders); + this.kernelObj = null; + this.variableMap = null; + this.outputViews = null; + this.inputViews = null; + this.staticBuffer = null; + this.dynamicBuffer = null; } - private compileKernel(): void { - var dnn_fallback_kernel: any; + private async compile(): Promise { + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + + let dnn_fallback_kernel: any = null; eval(this.descriptor.kernel_source); + this.kernelObj = dnn_fallback_kernel; } - async loadWeights(weightsData: Uint8Array): Promise { - // when weight format becomes not flat array (such as using quantization), the interface should be changed + private async initializeStaticBuffer(weightRawArray: ArrayBuffer) { + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + let descriptor = this.descriptor; + + let staticBuffer = new Float32Array(descriptor.memory_layout.static.size); + this.staticBuffer = staticBuffer; + + let variableMap = this.variableMap || new Map(); + this.variableMap = variableMap; + + Object.entries(descriptor.memory_layout.static.allocations) + .forEach(([name, allocation]: [string, ResolvedAllocation]) => { + variableMap.set( + name, + new Float32Array( + staticBuffer.buffer, + allocation.offset * Float32Array.BYTES_PER_ELEMENT, + allocation.size + ) + ); + }); + let decoder = get_weight_decoder(this.descriptor.weight_encoding); - this.rawWeightArray.set(await decoder.decode(weightsData, this.descriptor.weight_allocation)); + staticBuffer.set(await decoder.decode(new Uint8Array(weightRawArray), this.descriptor.memory_layout)); + + (await this.getInputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer(staticBuffer.buffer)); + + (await this.getOutputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer(staticBuffer.buffer)); + } + + private async initializeDynamicBuffer() { + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + + let dynamicBuffer = new Float32Array(placeholderContext.resolve(descriptor.memory_layout.dynamic.size)); + this.dynamicBuffer = dynamicBuffer; + + let variableMap = this.variableMap || new Map(); + this.variableMap = variableMap; + + Object.entries(descriptor.memory_layout.dynamic.allocations) + .forEach(([name, allocation]: [string, Allocation]) => { + variableMap.set( + name, + new Float32Array( + dynamicBuffer.buffer, + placeholderContext.resolve(allocation.offset) * Float32Array.BYTES_PER_ELEMENT, + placeholderContext.resolve(allocation.size) + ) + ); + }); + + (await this.getInputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer(dynamicBuffer.buffer)); + + (await this.getOutputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer(dynamicBuffer.buffer)); + } + + async setPlaceholderValue(values: { [key: string]: number; }) { + if (!this.placeholderContext) throw new Error('placeholderContext is not initialized'); + let placeholderContext = this.placeholderContext; + + placeholderContext.update(values); + if (!placeholderContext.isResolved) return; + + await this.initializeDynamicBuffer(); } async run(): Promise { - if (!this.inputViews || !this.outputViews) { - throw new Error('getInputViews and getOutputViews must be called prior to run'); - } - let run_entry_date = Date.now(); - let last_progress_date = Date.now();//in milliseconds - for (let i = 0; i < this.descriptor.exec_infos.length; i++) { - let current_date = Date.now(); - if (current_date - last_progress_date >= 1000) { - let elapsed_ms = current_date - run_entry_date; - console.log(`Processed ${i}/${this.descriptor.exec_infos.length} kernels in ${elapsed_ms} ms`); - last_progress_date = current_date; - await this.wait_to_display(); + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) throw new Error('placeholderContext is not initialized'); + if (!this.variableMap) throw new Error('Variable map is not initialized'); + if (!this.staticBuffer) throw new Error('StaticBuffer map is not initialized'); + if (!this.dynamicBuffer) throw new Error('DynamicBuffer map is not initialized'); + if (!this.inputViews || !this.outputViews) throw new Error('getInputViews() and getOutputViews() must be called prior to run'); + + let variableMap = this.variableMap; + let placeholderContext = this.placeholderContext; + let executionInfos = this.descriptor.exec_infos + .map(executionInfo => placeholderContext.resolve(executionInfo)); + + let startDate = Date.now(); + let lastDate = Date.now(); + + for (let i = 0; i < executionInfos.length; i++) { + let currentDate = Date.now(); + if (currentDate - lastDate >= 1000) { + console.log(`Processed ${i}/${executionInfos.length} kernels in ${currentDate - startDate} ms`); + lastDate = currentDate; + + await wait(); } - let exec_info = this.descriptor.exec_infos[i]; - let input_arrays = exec_info.inputs.map((name) => this.variableArrays.get(name)); - let output_arrays = exec_info.outputs.map((name) => this.variableArrays.get(name)); - let weight_arrays = exec_info.weights.map((name) => this.weightArrays.get(name)); - this.kernelObj[exec_info.entry_func_name](input_arrays, output_arrays, weight_arrays, exec_info.call_option); + + let executionInfo = executionInfos[i]; + let inputs = executionInfo.inputs.map((name) => variableMap.get(name)); + let outputs = executionInfo.outputs.map((name) => variableMap.get(name)); + this.kernelObj[executionInfo.entry_func_name](inputs, outputs, executionInfo.call_option); } - console.log(`Processed ${this.descriptor.exec_infos.length}/${this.descriptor.exec_infos.length} kernels in ${Date.now() - run_entry_date} ms`); + console.log(`Processed ${executionInfos.length}/${executionInfos.length} kernels in ${Date.now() - startDate} ms`); } - async wait_to_display() { - // let console.log to be displayed, and prevent freeze - return new Promise(function (resolve, reject) { - setTimeout(resolve, 10); + getInputViews() { + if (this.inputViews) return this.inputViews; + + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + + this.inputViews = descriptor.inputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new SymbolicFloat32Array(allocation, placeholderContext); + + return view; }); - } - async getInputViews(): Promise { - if (this.inputViews) { - return this.inputViews; - } - let views = this.descriptor.inputs.map((name) => this.variableArrays.get(name)!); - this.inputViews = views; - return views; + return this.inputViews; } - async getOutputViews(): Promise { - if (this.outputViews) { - return this.outputViews; - } - let views = this.descriptor.outputs.map((name) => this.variableArrays.get(name)!); - this.outputViews = views; - return views; + getOutputViews() { + if (this.outputViews) return this.outputViews; + + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized'); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + + this.outputViews = descriptor.outputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new SymbolicFloat32Array(allocation, placeholderContext); + + return view; + }); + + return this.outputViews; } } } diff --git a/src/descriptor_runner/descriptor_runner/descriptor_runner_webassembly.ts b/src/descriptor_runner/descriptor_runner/descriptor_runner_webassembly.ts index 141b0e60a..67e413a77 100644 --- a/src/descriptor_runner/descriptor_runner/descriptor_runner_webassembly.ts +++ b/src/descriptor_runner/descriptor_runner/descriptor_runner_webassembly.ts @@ -4,33 +4,40 @@ /// /// +declare let WebAssembly; + namespace WebDNN { - export class DescriptorRunnerWebassembly implements DescriptorRunner { - private inputViews: Float32Array[]; - private outputViews: Float32Array[]; - private worker: Worker; - public descriptor: GraphDescriptorWebassembly; - public ignoreCache: boolean = false; - public backend: string = 'webassembly'; + export class DescriptorRunnerWebassembly extends DescriptorRunner { + readonly backendName = 'webassembly'; + + private inputViews: SymbolicFloat32Array[] | null; + private outputViews: SymbolicFloat32Array[] | null; + private worker: Worker | null; private worker_entry_js_path; private worker_promise_reject_func: any = null; private worker_initial_error: any = null; - constructor() { + constructor(option?: any) { + super(); + if (typeof Worker === 'undefined') { + throw new Error('WebWorker is needed for WebAssembly backend'); + } + if (typeof WebAssembly !== 'object') { + console.warn('WebAssembly is not supported on this browser, trying to use asm.js code'); + } + } + init(): Promise { + //nothing to do + return Promise.resolve(); } async load(directory: string, progressCallback?: (loaded: number, total: number) => any) { - let graph_url = `${directory}/graph_${this.backend}.json`; - if (this.ignoreCache) { - graph_url += '?t=' + Date.now(); - } - graph_url = transformUrl(graph_url); - let graph_fetch = await WebDNN.fetch(graph_url); - if (!graph_fetch.ok) { - throw new Error(`${graph_url} cannot be loaded`); - } + let graph_url = `${directory}/graph_${this.backendName}.json`; + let graph_fetch = await WebDNN.fetch(graph_url, {ignoreCache: this.ignoreCache}); + this.descriptor = await graph_fetch.json(); + this.placeholderContext = new PlaceholderContext(this.descriptor!.placeholders); // for browsers which does not support wasm, try asm.js code let kernel_backend = typeof WebAssembly === 'object' ? 'webassembly' : 'asmjs'; @@ -43,23 +50,78 @@ namespace WebDNN { await this.compile(); - let weight_url = `${directory}/weight_${this.backend}.bin`; - if (this.ignoreCache) { - weight_url += '?t=' + Date.now(); - } - weight_url = transformUrl(weight_url); - let weights_data_ab = await readArrayBufferProgressively(await WebDNN.fetch(weight_url), progressCallback); + let weight_url = `${directory}/weight_${this.backendName}.bin`; + let weight_fetch = await WebDNN.fetch(weight_url, {ignoreCache: this.ignoreCache}); + let weights_data_ab = await readArrayBufferProgressively(weight_fetch, progressCallback); await this.loadWeights(new Uint8Array(weights_data_ab)); + + //assign buffer to input/output buffer view + (await this.getInputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer((new Float32Array(view.length)).buffer)); + + (await this.getOutputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer((new Float32Array(view.length)).buffer)) } - setDescriptor(descriptor: GraphDescriptorWebassembly) { - this.descriptor = descriptor; + async setPlaceholderValue(values: { [key: string]: number }): Promise { + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized.'); + let placeholderContext = this.placeholderContext; + + placeholderContext.update(values); + if (!placeholderContext.isResolved) return; + + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + + let descriptor = this.descriptor; + let unresolvedValueLists = descriptor.unresolved_value_lists; + + let metaBufferFillList: number[] = []; + for (let kernel_order = 0; kernel_order < unresolvedValueLists.length; kernel_order++) { + let unresolvedValueList = unresolvedValueLists[kernel_order]; + unresolvedValueList.forEach((offset_placeholder) => { + let resolved_value = placeholderContext.resolve(offset_placeholder.placeholder); + metaBufferFillList.push(kernel_order, offset_placeholder.offset, resolved_value); + }); + } + + (await this.getInputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer((new Float32Array(view.length)).buffer)); + + (await this.getOutputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer((new Float32Array(view.length)).buffer)); + + let dynamicBufferSize = this.placeholderContext.resolve(this.descriptor.memory_layout.dynamic.size); + + await this.setPlaceholderValueWorker(dynamicBufferSize, new Int32Array(metaBufferFillList)); } - compile(): Promise { - this.worker = new Worker(this.worker_entry_js_path); - this.worker.onerror = (event) => { - console.error('Worker Exception: ' + event.message); + private setPlaceholderValueWorker(dynamicBufferSize: number, metaBufferFillArray: Int32Array): Promise { + if (!this.worker) throw Error("Worker is not initialized"); + let worker = this.worker; + return new Promise((resolve, reject) => { + worker.onmessage = (event) => { + if (event.data === 0) { + resolve(); + } else { + console.log(event.data); + worker.terminate(); + reject(new Error(event.data)); + } + }; + + worker.postMessage({type: 'set_dynamic_buffer', size: dynamicBufferSize, data: metaBufferFillArray}); + }); + } + + private compile(): Promise { + let worker = new Worker(this.worker_entry_js_path); + worker.onerror = (event) => { + console.error(event); + // console.error('Worker Exception: ' + event.message); if (this.worker_promise_reject_func) { this.worker_promise_reject_func(event); } else { @@ -67,102 +129,147 @@ namespace WebDNN { } }; let promise = new Promise((resolve, reject) => { - if (this.worker_initial_error) { - // occurs when this.worker_entry_js_path is 404 - reject(this.worker_initial_error); - return; - } + // occurs when this.worker_entry_js_path is 404 + if (this.worker_initial_error) return reject(this.worker_initial_error); + this.worker_promise_reject_func = reject; - this.worker.onmessage = (event) => { + worker.onmessage = (event) => { if (event.data === 0) { resolve(); } else { - this.worker.terminate(); + console.error(event.data); + worker.terminate(); reject(new Error(event.data)); } }; - //this.worker.postMessage({ type: 'init' }); }); + this.worker = worker; return promise; } - async loadWeights(weightsData: Uint8Array) { + private async loadWeights(weightsData: Uint8Array) { + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.worker) throw new Error('Worker is not initialized'); + let decoder = get_weight_decoder(this.descriptor.weight_encoding); - let weight_data = await decoder.decode(weightsData, this.descriptor.weight_allocation); + let weight_data = await decoder.decode(weightsData, this.descriptor.memory_layout); + let worker = this.worker; + let promise = new Promise((resolve, reject) => { this.worker_promise_reject_func = reject; - this.worker.onmessage = (event) => { + worker.onmessage = (event) => { if (event.data === 0) { resolve(); } else { - this.worker.terminate(); + console.log(event.data); + worker.terminate(); reject(new Error(event.data)); } }; - this.worker.postMessage({type: 'weight', data: weight_data}); + worker.postMessage({type: 'weight', data: weight_data}); }); return promise; } - async getInputViews(): Promise { - if (this.inputViews) { - return this.inputViews; - } - let views: Float32Array[] = []; - for (let i = 0; i < this.descriptor.inputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.inputs[i]]; - views.push(new Float32Array(var_alloc.size)); - } - this.inputViews = views; - return views; + getInputViews() { + if (this.inputViews) return this.inputViews; + + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized'); + + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + + this.inputViews = descriptor.inputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new SymbolicFloat32Array(allocation, placeholderContext, true); + + return view; + }); + + return this.inputViews; } - async getOutputViews(): Promise { - if (this.outputViews) { - return this.outputViews; - } - let views: Float32Array[] = []; - for (let i = 0; i < this.descriptor.outputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.outputs[i]]; - views.push(new Float32Array(var_alloc.size)); - } - this.outputViews = views; - return views; + getOutputViews() { + if (this.outputViews) return this.outputViews; + + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized'); + + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + + this.outputViews = descriptor.outputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + // buffer for SymbolicFloat32Array is dedicated for IO, since computation is performed on separate memory space. + let view = new SymbolicFloat32Array(allocation, placeholderContext, true); + + return view; + }); + + return this.outputViews; } async run(): Promise { - if (!this.inputViews || !this.outputViews) { - throw new Error('getInputViews and getOutputViews must be called prior to run'); - } + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.inputViews || !this.outputViews) throw new Error('getInputViews and getOutputViews must be called prior to run'); + if (!this.worker) throw new Error('Worker is not initialized'); + + let descriptor = this.descriptor; + let worker = this.worker; + let inputViews = this.inputViews; + let outputViews = this.outputViews; + let promise = new Promise((resolve, reject) => { // TODO: better way not to generate function on every run this.worker_promise_reject_func = reject; - this.worker.onmessage = (event) => { + worker.onmessage = (event) => { if (Array.isArray(event.data)) { for (let i = 0; i < event.data.length; i++) { - this.outputViews[i].set(event.data[i]); + outputViews[i].set(event.data[i]); } resolve(); } else { - this.worker.terminate(); + console.log(event.data); + worker.terminate(); reject(new Error(event.data)); } }; + let allocations = [descriptor.memory_layout.static.allocations, descriptor.memory_layout.dynamic.allocations]; let inputs: any = []; - for (let i = 0; i < this.descriptor.inputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.inputs[i]]; - inputs.push({offset: var_alloc.offset, size: var_alloc.size, data: this.inputViews[i]}); + for (let i = 0; i < descriptor.inputs.length; i++) { + for (let allocation_space = 0; allocation_space < 2; allocation_space++) { + let var_alloc = allocations[allocation_space][descriptor.inputs[i]]; + if (var_alloc) { + let symAb = inputViews[i]; + inputs.push({ + space: allocation_space, + offset: symAb.offset, + size: symAb.length, + data: symAb.toActual() + }); + break; + } + } } + let outputs: any = []; - for (let i = 0; i < this.descriptor.outputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.outputs[i]]; - outputs.push({offset: var_alloc.offset, size: var_alloc.size}); + for (let i = 0; i < descriptor.outputs.length; i++) { + for (let allocation_space = 0; allocation_space < 2; allocation_space++) { + let var_alloc = allocations[allocation_space][descriptor.outputs[i]]; + if (var_alloc) { + let symAb = outputViews[i]; + outputs.push({space: allocation_space, offset: symAb.offset, size: symAb.length}); + break; + } + } } - this.worker.postMessage({type: 'run', inputs: inputs, outputs: outputs}); + + worker.postMessage({type: 'run', inputs: inputs, outputs: outputs}); }); return promise; diff --git a/src/descriptor_runner/descriptor_runner/descriptor_runner_webgpu.ts b/src/descriptor_runner/descriptor_runner/descriptor_runner_webgpu.ts index 33d977da0..e61fc073e 100644 --- a/src/descriptor_runner/descriptor_runner/descriptor_runner_webgpu.ts +++ b/src/descriptor_runner/descriptor_runner/descriptor_runner_webgpu.ts @@ -4,110 +4,231 @@ /// /// /// +/// +/// + +const IS_IOS = navigator.userAgent.includes('iPhone'); namespace WebDNN { - export class DescriptorRunnerWebGPU implements DescriptorRunner { - private descriptor: GraphDescriptorWebGPU; - private weightMat: BufferWebGPU; - private dataMat: BufferWebGPU; - private metaBufferGPUBuffers: BufferWebGPU[]; - public ignoreCache: boolean = false; - public backend: string = 'webgpu'; - private inputViews: Float32Array[]; - private outputViews: Float32Array[]; - - constructor(private webGPUHandler: WebGPUHandler) { + export class DescriptorRunnerWebGPU extends DescriptorRunner { + readonly backendName = 'webgpu'; + + private webgpuHandler: WebGPUHandler; + private shaderLanguage: string; + + private staticBuffer: BufferWebGPU | null; + private dynamicBuffer: BufferWebGPU | null; + private metaBuffers: BufferWebGPU[] | null; + + private inputViews: SymbolicFloat32Array[] | null; + private outputViews: SymbolicFloat32Array[] | null; + private executionInfos: GraphDescriptorWebGPUExecInfos[] | null; + + //noinspection JSUnusedLocalSymbols + constructor(option?: any) { + super(); + if (!WebGPUHandler.isBrowserSupported) { + throw new Error('WebGPU is not supported on this browser'); + } + } + + async init() { + // initialize webgpu, build kernels + this.shaderLanguage = 'metal'; + this.webgpuHandler = new WebGPUHandler(); + await this.webgpuHandler.init(); + BufferWebGPU.init(this.webgpuHandler); + + this.initializeBasicKernels(); + } + + private initializeBasicKernels() { + this.webgpuHandler.loadKernel('kernel void sync(){}', 'basic'); } async load(directory: string, progressCallback?: (loaded: number, total: number) => any) { - let graph_url = `${directory}/graph_${this.backend}.json`; - if (this.ignoreCache) { - graph_url += '?t=' + Date.now(); - } - graph_url = transformUrl(graph_url); - let graph_fetch = await WebDNN.fetch(graph_url); - if (!graph_fetch.ok) { - throw new Error(`${graph_url} cannot be loaded`); - } - this.descriptor = await graph_fetch.json(); + let [descriptor, weightRawArray] = await Promise.all([ + WebDNN.fetch(`${directory}/graph_${this.backendName}.json`, {ignoreCache: this.ignoreCache}) + .then(res => res.json() as Promise), + + WebDNN.fetch(`${directory}/weight_${this.backendName}.bin`, {ignoreCache: this.ignoreCache}) + .then(res => readArrayBufferProgressively(res, progressCallback)) + ]); + + await this.setDescriptor(descriptor); await this.compile(); + await this.initializeStaticBuffer(weightRawArray); + await this.initializeMetaBuffers(); - let weight_url = `${directory}/weight_${this.backend}.bin`; - if (this.ignoreCache) { - weight_url += '?t=' + Date.now(); - } - weight_url = transformUrl(weight_url); - let weights_data_ab = await readArrayBufferProgressively(await WebDNN.fetch(weight_url, progressCallback), progressCallback); - await this.loadWeights(new Uint8Array(weights_data_ab)); + await this.setPlaceholderValue({ + '__MAX_THREADS_PER_THREADGROUP__': IS_IOS ? 512 : 512 + }); + if (this.placeholderContext && this.placeholderContext.isResolved) await this.initializeDynamicBuffer(); + } + + private async initializeStaticBuffer(weightRawArray: ArrayBuffer) { + if (!this.descriptor) throw Error("GraphDescriptor is not loaded."); + let descriptor = this.descriptor; + + let staticBuffer = new BufferWebGPU(descriptor.memory_layout.static.size * Float32Array.BYTES_PER_ELEMENT); + this.staticBuffer = staticBuffer; + + let decoder = get_weight_decoder(descriptor.weight_encoding); + await staticBuffer.write(await decoder.decode(new Uint8Array(weightRawArray), descriptor.memory_layout)); + + (await this.getInputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer(staticBuffer.bufferView.buffer)); + + (await this.getOutputViews()) + .filter(view => !view.isDynamic) + .forEach(view => view.setArrayBuffer(staticBuffer.bufferView.buffer)); + } + + private async initializeMetaBuffers() { + if (!this.descriptor) throw Error("GraphDescriptor is not loaded."); + + this.metaBuffers = await Promise.all( + this.descriptor.exec_infos.map(async executionInfo => { + let buffer = new BufferWebGPU(executionInfo.meta_buffer.length * Int32Array.BYTES_PER_ELEMENT); + await buffer.write(new Uint8Array(executionInfo.meta_buffer)); + + return buffer; + }) + ); + } + + private async initializeDynamicBuffer() { + if (!this.descriptor) throw Error("GraphDescriptor is not loaded."); + if (!this.placeholderContext) throw Error("PlaceholderContext is not initialized."); + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + + let dynamicBufferSize = placeholderContext.resolve(descriptor.memory_layout.dynamic.size); + let dynamicBuffer = new BufferWebGPU(dynamicBufferSize * Float32Array.BYTES_PER_ELEMENT); + this.dynamicBuffer = dynamicBuffer; + + (await this.getInputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer(dynamicBuffer.bufferView.buffer)); + + (await this.getOutputViews()) + .filter(view => view.isDynamic) + .forEach(view => view.setArrayBuffer(dynamicBuffer.bufferView.buffer)); } - setDescriptor(descriptor: GraphDescriptorWebGPU) { + private async setDescriptor(descriptor: GraphDescriptorWebGPU) { this.descriptor = descriptor; + + //reset all datum depend on old descriptor + this.staticBuffer = null; + this.dynamicBuffer = null; + this.metaBuffers = null; + this.placeholderContext = new PlaceholderContext(descriptor.placeholders); + this.executionInfos = descriptor.exec_infos; } - async compile() { - this.webGPUHandler.loadKernel(this.descriptor.kernel_source, 'descriptor'); - this.weightMat = new BufferWebGPU(this.descriptor.weight_allocation.total_size * Float32Array.BYTES_PER_ELEMENT); - this.dataMat = new BufferWebGPU(this.descriptor.variable_allocation.total_size * Float32Array.BYTES_PER_ELEMENT); - this.metaBufferGPUBuffers = []; - for (let i = 0; i < this.descriptor.exec_infos.length; i++) { - let exec_info = this.descriptor.exec_infos[i]; - let buf = new BufferWebGPU(exec_info.meta_buffer.length * Float32Array.BYTES_PER_ELEMENT); - await buf.write(new Uint8Array(exec_info.meta_buffer)); - this.metaBufferGPUBuffers.push(buf); - } + private async compile() { + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + + this.webgpuHandler.loadKernel(this.descriptor.kernel_source, 'descriptor'); } - async loadWeights(weightsData: Uint8Array) { - let decoder = get_weight_decoder(this.descriptor.weight_encoding); - await this.weightMat.write(await decoder.decode(weightsData, this.descriptor.weight_allocation)); + async setPlaceholderValue(values: { [key: string]: number }) { + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized.'); + let placeholderContext = this.placeholderContext; + + placeholderContext.update(values); + if (!placeholderContext.isResolved) return; + + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.metaBuffers) throw new Error('MetaBuffers are not initialized'); + + let descriptor = this.descriptor; + let metaBuffers = this.metaBuffers; + + await this.initializeDynamicBuffer(); + + // resolve placeholders in execution info + this.executionInfos = await Promise.all( + descriptor.exec_infos.map(async (executionInfo, i) => { + + // resolve placeholders in meta buffer + let bufferView = new Int32Array(metaBuffers[i].bufferView.buffer); + for (let unresolved_value of executionInfo.unresolved_value_list) { + bufferView[unresolved_value.offset] = placeholderContext.resolve(unresolved_value.placeholder); + } + + return placeholderContext.resolve(executionInfo); + }) + ); } - async getInputViews(): Promise { - if (this.inputViews) { - return this.inputViews; - } - let views: Float32Array[] = []; - for (let i = 0; i < this.descriptor.inputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.inputs[i]]; - views.push(this.dataMat.getWriteView(var_alloc.offset, var_alloc.size, Float32Array)); - } - this.inputViews = views; - return views; + getInputViews() { + if (this.inputViews) return this.inputViews; + + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized'); + + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + + this.inputViews = descriptor.inputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new SymbolicFloat32Array(allocation, placeholderContext); + + return view; + }); + + return this.inputViews; } - async getOutputViews(): Promise { - if (this.outputViews) { - return this.outputViews; - } - let views: Float32Array[] = []; - for (let i = 0; i < this.descriptor.outputs.length; i++) { - let var_alloc = this.descriptor.variable_allocation.allocation[this.descriptor.outputs[i]]; - views.push(this.dataMat.getReadView(var_alloc.offset, var_alloc.size, Float32Array)); - } - this.outputViews = views; - return views; + getOutputViews() { + if (this.outputViews) return this.outputViews; + + if (!this.descriptor) throw new Error('Descriptor is not loaded'); + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized'); + + let descriptor = this.descriptor; + let placeholderContext = this.placeholderContext; + + this.outputViews = descriptor.outputs.map(name => { + let allocation = descriptor.memory_layout.static.allocations[name] || descriptor.memory_layout.dynamic.allocations[name]; + let view = new SymbolicFloat32Array(allocation, placeholderContext); + + return view; + }); + + return this.outputViews; } async run(): Promise { - if (!this.inputViews || !this.outputViews) { - throw new Error('getInputViews and getOutputViews must be called prior to run'); - } + if (!this.executionInfos) throw new Error('ExecutionInfos is not loaded'); + if (!this.inputViews || !this.outputViews) throw new Error('getInputViews and getOutputViews must be called prior to run'); + if (!this.staticBuffer) throw new Error('StaticBuffer is not initialized'); + if (!this.dynamicBuffer) throw new Error('DynamicBuffer is not initialized'); + if (!this.metaBuffers) throw new Error('MetaBuffer is not initialized'); + if (!this.placeholderContext) throw new Error('PlaceholderContext is not initialized'); + if (!this.placeholderContext.isResolved) throw new Error(`Not all placeholders are resolved: ${this.placeholderContext}`); + + let staticBuffer = this.staticBuffer; + let dynamicBuffer = this.dynamicBuffer; + let metaBuffers = this.metaBuffers; - if (window['PROFILE']) { + if (WebDNN.DEBUG) { let records: any = []; let totalElapsedTime = 0; - for (let i = 0; i < this.descriptor.exec_infos.length; i++) { - let exec_info = this.descriptor.exec_infos[i]; + for (let i = 0; i < this.executionInfos.length; i++) { + let exec_info = this.executionInfos[i]; let start = performance.now(); - await this.webGPUHandler.executeSinglePipelineState( + await this.webgpuHandler.executeSinglePipelineState( 'descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, - [this.weightMat, this.dataMat, this.metaBufferGPUBuffers[i]], + [staticBuffer, dynamicBuffer, metaBuffers[i]], true ); let elapsedTime = performance.now() - start; @@ -140,14 +261,14 @@ namespace WebDNN { } else { let complete_promise: Promise | null = null; - for (let i = 0; i < this.descriptor.exec_infos.length; i++) { - let exec_info = this.descriptor.exec_infos[i]; - let is_last = i == this.descriptor.exec_infos.length - 1; - complete_promise = this.webGPUHandler.executeSinglePipelineState( + for (let i = 0; i < this.executionInfos.length; i++) { + let exec_info = this.executionInfos[i]; + let is_last = i == this.executionInfos.length - 1; + complete_promise = this.webgpuHandler.executeSinglePipelineState( 'descriptor.' + exec_info.entry_func_name, exec_info.threadgroups_per_grid, exec_info.threads_per_thread_group, - [this.weightMat, this.dataMat, this.metaBufferGPUBuffers[i]], + [staticBuffer, dynamicBuffer, metaBuffers[i]], is_last ); } diff --git a/src/descriptor_runner/fetch.ts b/src/descriptor_runner/fetch.ts index 2217d6fb6..a3cf47e28 100644 --- a/src/descriptor_runner/fetch.ts +++ b/src/descriptor_runner/fetch.ts @@ -11,6 +11,10 @@ let transformDelegate: (base: string) => string = url => url; let fetchDelegate: (input: RequestInfo, init?: RequestInit) => Promise = window.fetch; namespace WebDNN { + export interface WebDNNRequestInit extends RequestInit { + ignoreCache: boolean + } + /** * Register delegate function for transform url * @param url url which will be transformed @@ -39,10 +43,21 @@ namespace WebDNN { * Fetch function. WebDNN API use this fetch function instead of original fetch function. * @param input Requested url * @param init Additional information about fetch + * @param init.ignoreCache If true, cache is ignored by appending '?t=(timestamp)' to the end of request url. * @returns Response */ - export function fetch(input: RequestInfo, init?: RequestInit) { - return fetchDelegate(input, init); + export async function fetch(input: RequestInfo, init?: WebDNNRequestInit) { + if (typeof input == 'string') { + input = transformUrl(input) + ((init && init.ignoreCache) ? '?t=' + Date.now() : ''); + } else { + input = Object.assign({}, input, { + url: transformUrl(input.url) + ((init && init.ignoreCache) ? '?t=' + Date.now() : '') + }); + } + + let res = await fetchDelegate(input, init); + if (!res.ok) throw new Error(`Fetch returns status code ${res.status}: ${res.statusText}`); + return res; } /** diff --git a/src/descriptor_runner/gpu_interface/gpu_interface.ts b/src/descriptor_runner/gpu_interface/gpu_interface.ts deleted file mode 100644 index d99fba2c9..000000000 --- a/src/descriptor_runner/gpu_interface/gpu_interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// - -namespace WebDNN { - export interface GPUInterface { - init(): Promise; - createDescriptorRunner(): DescriptorRunner; - } -} diff --git a/src/descriptor_runner/gpu_interface/gpu_interface_fallback.ts b/src/descriptor_runner/gpu_interface/gpu_interface_fallback.ts deleted file mode 100644 index 2fb501b56..000000000 --- a/src/descriptor_runner/gpu_interface/gpu_interface_fallback.ts +++ /dev/null @@ -1,16 +0,0 @@ -/// - -namespace WebDNN { - export class GPUInterfaceFallback implements GPUInterface { - constructor(private option?: any) { - - } - - async init(option?: any) { - } - - createDescriptorRunner(): DescriptorRunner { - return new DescriptorRunnerFallback(); - } - } -} diff --git a/src/descriptor_runner/gpu_interface/gpu_interface_webassembly.ts b/src/descriptor_runner/gpu_interface/gpu_interface_webassembly.ts deleted file mode 100644 index 517bec27b..000000000 --- a/src/descriptor_runner/gpu_interface/gpu_interface_webassembly.ts +++ /dev/null @@ -1,26 +0,0 @@ -/// -/// - -declare var WebAssembly; - -namespace WebDNN { - export class GPUInterfaceWebassembly implements GPUInterface { - - constructor(private option?: any) { - if (typeof Worker === 'undefined') { - throw new Error('WebWorker is needed for WebAssembly backend'); - } - if (typeof WebAssembly !== 'object') { - console.warn('WebAssembly is not supported on this browser, trying to use asm.js code'); - } - } - - async init() { - } - - - createDescriptorRunner(): DescriptorRunner { - return new DescriptorRunnerWebassembly(); - } - } -} diff --git a/src/descriptor_runner/gpu_interface/gpu_interface_webgpu.ts b/src/descriptor_runner/gpu_interface/gpu_interface_webgpu.ts deleted file mode 100644 index bf3216672..000000000 --- a/src/descriptor_runner/gpu_interface/gpu_interface_webgpu.ts +++ /dev/null @@ -1,33 +0,0 @@ -/// - -declare let WebGPUComputeCommandEncoder; - -namespace WebDNN { - export class GPUInterfaceWebGPU implements GPUInterface { - webgpuHandler: WebGPUHandler; - shaderLanguage: string; - - constructor(private option?: any) { - if (!WebGPUHandler.isBrowserSupported) { - throw new Error('WebGPU is not supported on this browser'); - } - } - - async init() { - // initialize webgpu, build kernels - this.shaderLanguage = 'metal'; - this.webgpuHandler = new WebGPUHandler(); - await this.webgpuHandler.init(); - BufferWebGPU.init(this.webgpuHandler); - this.init_basic_kernels(); - } - - private init_basic_kernels() { - this.webgpuHandler.loadKernel('kernel void sync(){}', 'basic'); - } - - createDescriptorRunner(): DescriptorRunner { - return new DescriptorRunnerWebGPU(this.webgpuHandler); - } - } -} diff --git a/src/descriptor_runner/graph_descriptor/graph_descriptor.ts b/src/descriptor_runner/graph_descriptor/graph_descriptor.ts index 16805d3b4..8d20cad4f 100644 --- a/src/descriptor_runner/graph_descriptor/graph_descriptor.ts +++ b/src/descriptor_runner/graph_descriptor/graph_descriptor.ts @@ -14,22 +14,18 @@ namespace WebDNN { outputs: string[]; /** - * Allocation information for each variable. + * memory position table */ - weight_allocation: { - allocation: { [name: string]: any } - }; - + memory_layout: MemoryLayout, + /** - * Allocation information for each variable. + * Encoding algorithm of weight binary data. */ - variable_allocation: { - allocation: { [name: string]: any } - }; + weight_encoding: string; /** - * Encoding algorithm of weight binary data. + * Placeholder dict */ - weight_encoding: string; + placeholders: { [key: string]: number } } } diff --git a/src/descriptor_runner/graph_descriptor/graph_descriptor_fallback.ts b/src/descriptor_runner/graph_descriptor/graph_descriptor_fallback.ts index 76478c52a..c7580ce52 100644 --- a/src/descriptor_runner/graph_descriptor/graph_descriptor_fallback.ts +++ b/src/descriptor_runner/graph_descriptor/graph_descriptor_fallback.ts @@ -2,14 +2,7 @@ namespace WebDNN { export interface GraphDescriptorFallback extends GraphDescriptor { - weight_allocation: { - total_size: number; - allocation: { [index: string]: { name: string, offset: number, size: number } } - }; - variable_allocation: { - total_size: number; - allocation: { [index: string]: { name: string, offset: number, size: number } } - }; + memory_layout: MemoryLayout; kernel_source: string; exec_infos: GraphDescriptorFallbackExecInfo[]; } diff --git a/src/descriptor_runner/graph_descriptor/graph_descriptor_webassembly.ts b/src/descriptor_runner/graph_descriptor/graph_descriptor_webassembly.ts index a4b00eab3..fbd11968a 100644 --- a/src/descriptor_runner/graph_descriptor/graph_descriptor_webassembly.ts +++ b/src/descriptor_runner/graph_descriptor/graph_descriptor_webassembly.ts @@ -2,13 +2,6 @@ namespace WebDNN { export interface GraphDescriptorWebassembly extends GraphDescriptor { - weight_allocation: { - total_size: number; - allocation: { [index: string]: { name: string, offset: number, size: number } } - }; - variable_allocation: { - total_size: number; - allocation: { [index: string]: { name: string, offset: number, size: number } } - }; + unresolved_value_lists: { offset: number, placeholder: Placeholder }[][]; } } diff --git a/src/descriptor_runner/graph_descriptor/graph_descriptor_webgpu.ts b/src/descriptor_runner/graph_descriptor/graph_descriptor_webgpu.ts index b10b67423..d4523ff9a 100644 --- a/src/descriptor_runner/graph_descriptor/graph_descriptor_webgpu.ts +++ b/src/descriptor_runner/graph_descriptor/graph_descriptor_webgpu.ts @@ -2,14 +2,7 @@ namespace WebDNN { export interface GraphDescriptorWebGPU extends GraphDescriptor { - weight_allocation: { - total_size: number; - allocation: { [index: string]: { name: string, offset: number, size: number } } - }; - variable_allocation: { - total_size: number; - allocation: { [index: string]: { name: string, offset: number, size: number } } - }; + memory_layout: MemoryLayout; kernel_source: string; exec_infos: GraphDescriptorWebGPUExecInfos[]; } @@ -19,5 +12,6 @@ namespace WebDNN { threadgroups_per_grid: WebGPUSize; threads_per_thread_group: WebGPUSize; meta_buffer: number[]; + unresolved_value_list: { offset: number, placeholder: Placeholder }[] } } diff --git a/src/descriptor_runner/math.ts b/src/descriptor_runner/math.ts index f0e9b8187..55383c5b6 100644 --- a/src/descriptor_runner/math.ts +++ b/src/descriptor_runner/math.ts @@ -7,6 +7,7 @@ namespace WebDNN { * @returns {number[]} indices of top-K largest samples */ export function argmax(arr: number[], k: number = 1) { + arr = arr.slice(); let stack = [[0, arr.length]]; let workspace: { [key: number]: number } = {}; @@ -61,6 +62,7 @@ namespace WebDNN { * @returns {number[]} indices of top-K smallest samples */ export function argmin(arr: number[], k: number = 1) { + arr = arr.slice(); let stack = [[0, arr.length]]; let workspace: { [key: number]: number } = {}; diff --git a/src/descriptor_runner/memory_layout.ts b/src/descriptor_runner/memory_layout.ts new file mode 100644 index 000000000..10f258bb8 --- /dev/null +++ b/src/descriptor_runner/memory_layout.ts @@ -0,0 +1,29 @@ +/// + +namespace WebDNN { + export interface Allocation { + name: string, + offset: number | Placeholder, + size: number | Placeholder + } + + export interface ResolvedAllocation extends Allocation { + offset: number, + size: number + } + + export interface MemoryLayout { + 'static': { + size: number, + allocations: { + [index: string]: ResolvedAllocation + } + }, + dynamic: { + size: number | Placeholder, + allocations: { + [index: string]: Allocation + } + } + } +} diff --git a/src/descriptor_runner/placeholder.ts b/src/descriptor_runner/placeholder.ts new file mode 100644 index 000000000..ee6263bfc --- /dev/null +++ b/src/descriptor_runner/placeholder.ts @@ -0,0 +1,54 @@ +namespace WebDNN { + export type Placeholder = { + eval: string + }; + + /** + * PlaceholderContext manages the placeholders + */ + export class PlaceholderContext { + private values: { [key: string]: number | null } = {}; + + constructor(values?: { [key: string]: number | null }) { + if (values) { + this.update(values); + } + } + + get isResolved() { + return Object.values(this.values).every(value => typeof value == 'number'); + } + + update(values: { [key: string]: number | null }) { + this.values = Object.assign(this.values, values); + } + + resolve(placeholder: any) : any { + // Literal value => return itself. + if (typeof placeholder !== 'object') return placeholder; + + // Placeholder object ( { eval: string } ) => resolve + if (Object.keys(placeholder).length == 1 && 'eval' in placeholder) { + if (!this.isResolved) throw Error(`Not all placeholders are resolved: ${this}`); + + return ((placeholders) => eval(placeholder.eval))(this.values); + } + + // Array => deep copy + if (placeholder instanceof Array) { + return placeholder.map((value: any) => this.resolve(value)); + } + + // Object => deep copy + return Object.entries(placeholder) + .reduce((result: Object, [key, value]: [string, any]) => { + result[key] = this.resolve(value); + return result; + }, {}) + } + + toString() { + return JSON.stringify(this.values); + } + } +} diff --git a/src/descriptor_runner/symbolic_array_buffer_view.ts b/src/descriptor_runner/symbolic_array_buffer_view.ts new file mode 100644 index 000000000..f37d98e1f --- /dev/null +++ b/src/descriptor_runner/symbolic_array_buffer_view.ts @@ -0,0 +1,91 @@ +/// +/// + +namespace WebDNN { + export abstract class SymbolicArrayBufferView { + protected arrayBuffer?: ArrayBuffer; + protected allocation: Allocation; + protected placeholderContext?: PlaceholderContext; + + /** + * Convert symbolic buffer view into actual buffer view. + * If this buffer view is initialized based on placeholder offset or size and the placeholder is not resolved, + * the error is thrown. + */ + abstract toActual(): T; + + constructor(allocation: Allocation, placeholderContext?: PlaceholderContext, protected ignoreOffsetOnActual: boolean = false) { + this.allocation = allocation; + + if (this.isDynamic) { + if (!placeholderContext) { + throw Error('PlaceholderContext must be required when SymbolicArrayBufferView is initialized as dynamic buffer view.') + } + } + + this.placeholderContext = placeholderContext; + } + + setArrayBuffer(arrayBuffer) { + this.arrayBuffer = arrayBuffer; + } + + get isDynamic() { + return (typeof this.allocation.offset !== 'number' || typeof this.allocation.size !== 'number') + } + + get offset() { + //TODO + if (this.isDynamic) { + return this.placeholderContext!.resolve(this.allocation.offset); + + } else { + return (this.allocation as ResolvedAllocation).offset; + } + } + + get length() { + if (this.isDynamic) { + return this.placeholderContext!.resolve(this.allocation.size); + + } else { + return (this.allocation as ResolvedAllocation).size; + } + } + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void { + return this.toActual().set(array, offset); + } + } + + export class SymbolicFloat32Array extends SymbolicArrayBufferView { + toActual() { + if (!this.arrayBuffer) { + throw new Error('Internal buffer for this variable is not set. DescriptorRunner.setPlaceholderValue() have to be called before calling this function.'); + } + return new Float32Array( + this.arrayBuffer, + this.ignoreOffsetOnActual ? 0 : this.offset * Float32Array.BYTES_PER_ELEMENT, + this.length + ); + } + } + + export class SymbolicInt32Array extends SymbolicArrayBufferView { + toActual() { + if (!this.arrayBuffer) { + throw new Error('Internal buffer for this variable is not set. DescriptorRunner.setPlaceholderValue() have to be called before calling this function.'); + } + return new Int32Array( + this.arrayBuffer, + this.ignoreOffsetOnActual ? 0 : this.offset * Int32Array.BYTES_PER_ELEMENT, + this.length + ); + } + } +} diff --git a/src/descriptor_runner/tsconfig.es5.json b/src/descriptor_runner/tsconfig.es5.json index 1f46fd0fa..42d2ead6b 100644 --- a/src/descriptor_runner/tsconfig.es5.json +++ b/src/descriptor_runner/tsconfig.es5.json @@ -14,6 +14,7 @@ "license.ts", "webdnn.ts", "math.ts", - "get_backend_availability.ts" + "get_backend_availability.ts", + "symbolic_array_buffer_view.ts" ] } diff --git a/src/descriptor_runner/tsconfig.json b/src/descriptor_runner/tsconfig.json index 040eb2158..91a84f4c6 100644 --- a/src/descriptor_runner/tsconfig.json +++ b/src/descriptor_runner/tsconfig.json @@ -14,6 +14,7 @@ "license.ts", "webdnn.ts", "math.ts", - "get_backend_availability.ts" + "get_backend_availability.ts", + "symbolic_array_buffer_view.ts" ] } diff --git a/src/descriptor_runner/webdnn.ts b/src/descriptor_runner/webdnn.ts index 118419e12..866c66d5b 100644 --- a/src/descriptor_runner/webdnn.ts +++ b/src/descriptor_runner/webdnn.ts @@ -1,61 +1,31 @@ -/// -/// -/// -/// +/// +/// +/// +/// namespace WebDNN { - export let gpu: GPUInterface; + export const backends = { + 'webgpu': DescriptorRunnerWebGPU, + 'webassembly': DescriptorRunnerWebassembly, + 'fallback': DescriptorRunnerFallback, + }; - let givenBackendOptions: { [key: string]: any }; - let tryingBackendOrder: string[]; - let loadedBackendName: string; + export let DEBUG: boolean = false; - async function tryInitNext(): Promise { - let backend_name = tryingBackendOrder.shift(); - if (!backend_name) { - throw new Error('No backend is available'); - } + async function initBackend(backendName: string, option?: any): Promise | null> { + if (!(backendName in backends)) throw new Error(`Unknown backend: "${backendName}"`); + + let runner: DescriptorRunner; - let option = givenBackendOptions[backend_name]; - let gpuif: GPUInterface; try { - switch (backend_name) { - case 'webgpu': - gpuif = new GPUInterfaceWebGPU(option); - break; - case 'webassembly': - gpuif = new GPUInterfaceWebassembly(option); - break; - case 'fallback': - gpuif = new GPUInterfaceFallback(option); - break; - default: - throw new Error('Unknown backend ' + backend_name); - } - await gpuif.init(); - gpu = gpuif; - loadedBackendName = backend_name; + runner = new backends[backendName](option); + await runner.init(); } catch (ex) { - console.warn(`Failed to initialize ${backend_name} backend: ${ex}`); - return await tryInitNext(); - } - - return loadedBackendName; - } - - export async function init(backendOrder?: string | string[], backendOptions: { [key: string]: any } = {}): Promise { - if (!backendOrder) { - backendOrder = ['webgpu', 'webassembly']; - } else if (typeof backendOrder === 'string') { - backendOrder = [backendOrder]; + console.warn(`Failed to initialize ${backendName} backend: ${ex}`); + return null; } - givenBackendOptions = backendOptions; - tryingBackendOrder = backendOrder.concat(['fallback']); - - await tryInitNext(); - - return loadedBackendName; + return runner; } /** @@ -67,6 +37,7 @@ namespace WebDNN { export interface InitOption { backendOrder?: string | string[], backendOptions?: { [key: string]: any }, + ignoreCache?: boolean, progressCallback?: (loaded: number, total: number) => any } @@ -76,50 +47,33 @@ namespace WebDNN { * @param initOption Initialize option * @return Interface to input/output data and run the model. */ - export async function prepareAll(directory: string, initOption: InitOption = {}): Promise { - await init(initOption.backendOrder, initOption.backendOptions); - - while (true) { - try { - let runner = gpu.createDescriptorRunner(); - await runner.load(directory, initOption.progressCallback); + export async function load(directory: string, initOption: InitOption = {}): Promise> { + let backendOrder = initOption.backendOrder; + if (!backendOrder) { + backendOrder = ['webgpu', 'webassembly']; + } else if (typeof backendOrder === 'string') { + backendOrder = [backendOrder]; + } + backendOrder = backendOrder.slice(); + if (backendOrder.indexOf('fallback') === -1) backendOrder.concat(['fallback']); - let inputViews = await runner.getInputViews(); - let outputViews = await runner.getOutputViews(); + let backendOptions = initOption.backendOptions || {}; - return { - backendName: loadedBackendName, - inputViews: inputViews, - outputViews: outputViews, - run: runner.run.bind(runner) - }; + while (backendOrder.length > 0) { + let backendName = backendOrder.shift()!; + let runner: DescriptorRunner | null = await initBackend(backendName, backendOptions[backendName]); + if (!runner) continue; + runner.ignoreCache = Boolean(initOption.ignoreCache); + try { + await runner.load(directory, initOption.progressCallback); } catch (ex) { - console.error(`Model loading failed for ${loadedBackendName} backend. Trying next backend. ${ex.message}`); - await tryInitNext(); + console.warn(`Model loading failed for ${backendName} backend. Trying next backend: ${ex.message}`); } + + return runner; } - } - /** - * Interface to input/output data and run the model. - */ - export interface GraphInterface { - /** - * The name of backend. - */ - backendName: string; - /** - * The buffers to write input data. - */ - inputViews: Float32Array[]; - /** - * The buffers to read output data. - */ - outputViews: Float32Array[]; - /** - * Run the model. - */ - run: () => Promise; + throw new Error('No backend is available'); } } diff --git a/src/descriptor_runner/webgpu_handler.ts b/src/descriptor_runner/webgpu_handler.ts index f26b02c10..60bb87337 100644 --- a/src/descriptor_runner/webgpu_handler.ts +++ b/src/descriptor_runner/webgpu_handler.ts @@ -8,12 +8,14 @@ namespace WebDNN { private pipelineStates: Map; async init() { - // asynchronous operation may be added in future - if (!WebGPUHandler.isBrowserSupported) { - throw new Error('This browser does not support WebGPU'); - } - this.context = (document.createElement('canvas').getContext('webgpu'));//force cast - this.commandQueue = this.context.createCommandQueue(); + if (!WebGPUHandler.isBrowserSupported) throw new Error('This browser does not support WebGPU'); + + let context = document.createElement('canvas').getContext('webgpu'); + if (!context) throw new Error('WebGPURenderingContext initialization failed'); + + this.context = context; + + this.commandQueue = context.createCommandQueue(); this.pipelineStates = new Map(); } @@ -146,3 +148,7 @@ declare interface WebGPUComputeCommandEncoder extends WebGPUCommandEncoder { declare interface WebGPUComputePipelineState { } + +declare interface HTMLCanvasElement { + getContext(contextId: "webgpu"): WebGPURenderingContext | null; +} diff --git a/src/graph_transpiler/webdnn/backend/webgpu/injectors/__init__.py b/src/graph_transpiler/webdnn/backend/code_generator/__init__.py similarity index 100% rename from src/graph_transpiler/webdnn/backend/webgpu/injectors/__init__.py rename to src/graph_transpiler/webdnn/backend/code_generator/__init__.py diff --git a/src/graph_transpiler/webdnn/backend/code_generator/allocator.py b/src/graph_transpiler/webdnn/backend/code_generator/allocator.py new file mode 100644 index 000000000..6fe44ac8a --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/code_generator/allocator.py @@ -0,0 +1,438 @@ +from enum import auto, Enum +from typing import Dict, List, Set, Tuple, Union, Optional + +import numpy as np + +from webdnn.graph import traverse +from webdnn.graph.graph import Graph +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.inplace import Inplace +from webdnn.graph.placeholder import Placeholder +from webdnn.graph.variable import Variable +from webdnn.graph.variables.constant_variable import ConstantVariable +from webdnn.util import json, flags + + +class BufferType(Enum): + Static = auto() + Dynamic = auto() + + +class Allocation(json.SerializableMixin): + variable: Variable + offset: Union[int, Placeholder] + buffer_type: BufferType + + def __init__(self, + variable: Variable, + offset: Union[int, Placeholder], + buffer_type: BufferType): + self.variable = variable + self.offset = offset + self.buffer_type = buffer_type + + @property + def size(self) -> Union[int, Placeholder]: + return self.variable.size + + def _to_serializable_(self): + return { + "name": self.variable.name, + "offset": self.offset, + "size": self.size + } + + +class MemoryLayout(json.SerializableMixin): + data: np.array + + def __init__(self): + self.allocations = {} # type: Dict[str, Allocation] + + def _to_serializable_(self): + return { + "static": { + "size": self.static_size, + "allocations": {a.variable.name: a for a in self.allocations.values() if + a.buffer_type == BufferType.Static} + }, + "dynamic": { + "size": self.dynamic_size, + "allocations": {a.variable.name: a for a in self.allocations.values() if + a.buffer_type == BufferType.Dynamic} + } + } + + def __len__(self): + return len(self.allocations) + + def __getitem__(self, var: Variable): + return self.allocations[var.name] + + def __contains__(self, var: Variable): + return var.name in self.allocations + + def append(self, var: Variable, offset: Union[int, Placeholder] = -1, buffer_type: Optional[BufferType] = None): + if buffer_type is None: + if Placeholder.check_resolved(offset) and Placeholder.check_resolved(var.size): + buffer_type = BufferType.Static + else: + buffer_type = BufferType.Dynamic + + if offset == -1: + if buffer_type is BufferType.Static: + offset = self.static_size + else: + offset = self.dynamic_size + + self.allocations[var.name] = Allocation(var, offset, buffer_type) + + @property + def total_size(self) -> Union[int, Placeholder]: + return self.static_size + self.dynamic_size + + @property + def static_size(self) -> int: + size = 0 + for a in self.allocations.values(): + if a.buffer_type == BufferType.Static: + size = max(a.offset + a.size, size) + + return size + + @property + def dynamic_size(self) -> Union[int, Placeholder]: + size = 0 + for a in self.allocations.values(): + if a.buffer_type == BufferType.Dynamic: + size += a.size + + return size + + +class Allocator: + layout: MemoryLayout + + @classmethod + def allocate(cls, graph: Graph) -> MemoryLayout: + variables = set(traverse.listup_variables(graph)) + for i, v in enumerate(variables): + v.name = f"v{i}" + + return cls.allocate_variables(graph, list(variables)) + + @classmethod + def allocate_variables(cls, graph: Graph, variables: List[Variable]): + # check if constant variable with shape with unresolved placeholder. + dynamic_constants = traverse.filter_nodes([v for v in variables if not Placeholder.check_resolved(v.size)], + ConstantVariable) + assert len( + dynamic_constants) == 0, f"ConstantVariable with unresolved placeholder shape is detected: f{dynamic_constants}" + + ops = traverse.listup_operators(graph) + layout = MemoryLayout() + + lifetime = get_lifetime(graph, ops, variables) # type: Dict[Variable, Tuple[int, int]] + offsets = generate_allocation_info(variables, lifetime) # type: Dict[Variable, Union[int, Placeholder]] + for variable, offset in offsets.items(): + layout.append(variable, offset) + + layout.data = np.zeros(layout.static_size, dtype=np.float32) + for var in variables: + if not isinstance(var, ConstantVariable): + continue + + allocation = layout[var] + layout.data[allocation.offset:allocation.offset + allocation.size] = var.data.flatten() + + if flags.VISUALIZE_MEMORY_ALLOCATION: + _visualize_allocation(ops, variables, layout, lifetime, offsets) + + return layout + + +def get_lifetime(graph: Graph, ops: List[Operator], variables: List[Variable]): + LIFETIME_FOREVER = len(ops) + 1 + + lifetime = {} # type: Dict[Variable, Tuple[int, int]] + retain_count = {v: 0 for v in variables} # type: Dict[Variable, int] + allocated = set() # type: Set[Variable] + + for var in variables: + if isinstance(var, ConstantVariable): + lifetime[var] = (0, LIFETIME_FOREVER) + allocated.add(var) + + for var in graph.inputs: + lifetime[var] = (0, LIFETIME_FOREVER) + allocated.add(var) + + for t, op in enumerate(ops): + for var in op.outputs.values(): + if isinstance(var, ConstantVariable): + continue + + if var not in allocated: + flag_allocated = False + + if flags.optimize.OPTIMIZE and flags.optimize.OPTIMIZE_INPLACE_OPERATION \ + and not flag_allocated \ + and traverse.check_attribute_match(op, Inplace): + + # Inplace optimization + inplace = op.get_attribute(Inplace)[0] # type: Inplace + v_in = inplace.get_input() # Use memory allocated for input variable + v_out = inplace.get_output() + if v_in.order == v_out.order: + while "inplace_src" in v_in.parameters: + v_in = v_in.parameters["inplace_src"] + + var.parameters["inplace_src"] = v_in + retain_count[v_in] += len(var.input_to) + + allocated.add(var) + flag_allocated = True + + if not flag_allocated: + lifetime[var] = (t, LIFETIME_FOREVER) + retain_count[var] = len(var.input_to) + + allocated.add(var) + flag_allocated = True + + if not flag_allocated: + raise ValueError("[Allocator] Memory Allocation Failed.") + + for var in op.inputs.values(): + if isinstance(var, ConstantVariable) or var in graph.inputs: + continue + + while "inplace_src" in var.parameters: + var = var.parameters["inplace_src"] + + if retain_count[var] == 0: + # var is temporally workspace memory + lifetime[var] = (t, t + 1) + + else: + retain_count[var] -= 1 + + if retain_count[var] == 0: + # `t + 1` means that `var` will be released AFTER `op` will be finished. + lifetime[var] = (lifetime[var][0], t + 1) + + return lifetime + + +def generate_allocation_info(variables: List[Variable], + lifetime: Dict[Variable, Tuple[int, int]]) -> Dict[Variable, int]: + """ + heuristic-based optimization + + 1. allocate constant variables first + 2. allocate unresolved shape variables last + 3. allocate variables which lives longer first + 4. allocate variables which released earlier first + 5. allocate larger variables first + """ + + static_variables = [v for v in variables if Placeholder.check_resolved(v.size)] + dynamic_variables = [v for v in variables if not Placeholder.check_resolved(v.size)] + + queue = filter(lambda x: x in lifetime, static_variables) + queue = sorted(queue, key=lambda x: x.size, reverse=True) + queue = sorted(queue, key=lambda x: lifetime[x][1]) + queue = sorted(queue, key=lambda x: lifetime[x][1] - lifetime[x][0], reverse=True) + queue = sorted(queue, key=lambda x: isinstance(x, ConstantVariable), reverse=True) + queue = list(queue) + + allocated_range = {} # type: Dict[int, List[Tuple[Union[int, Placeholder], Union[int, Placeholder]]]] + workspace = {v: [0, lifetime[v][0], lifetime[v][1]] for v in queue} + result = {} # type: Dict[Variable, int] + + while len(queue) > 0: + min_offset = +float("inf") + min_offset_v = None + + # find space + for v1 in queue: + info1 = workspace[v1] + offset1, start1, end1 = info1 + + flag_retry = True + while flag_retry: + flag_retry = False + + for t in range(start1, end1): + if t not in allocated_range: + continue + + for offset2, size2 in allocated_range[t]: + if offset2 + size2 <= offset1 or offset1 + v1.size <= offset2: + continue + + else: + # align for 16byte + offset1 = ((offset2 + size2 + 4 - 1) // 4) * 4 + flag_retry = True + break + + if flag_retry: + break + + info1[0] = offset1 + if offset1 < min_offset: + min_offset = offset1 + min_offset_v = v1 + + queue.remove(min_offset_v) + _, start1, end1 = workspace[min_offset_v] + + result[min_offset_v] = min_offset + for t in range(start1, end1): + if t not in allocated_range: + allocated_range[t] = [] + + allocated_range[t].append((min_offset, min_offset_v.size)) + + min_offset += min_offset_v.size + + for v in dynamic_variables: + result[v] = -1 # FIXME: optimize dynamic allocation + + for v1 in variables: + v2 = v1 + while "inplace_src" in v2.parameters: + v2 = v2.parameters["inplace_src"] + + result[v1] = result[v2] + + return result + + +def _visualize_allocation(ops: List[Operator], + variables: List[Variable], + layout: MemoryLayout, + lifetime: Dict[Variable, Tuple[int, int]], + offsets: Dict[Variable, Union[int, Placeholder]]): + UNIT_HEIGHT = 14 + total_size = layout.total_size + rendering_dict = {} # type: Dict[Variable, RenderingInfo] + + class RenderingInfo: + names: List[str] + v1: Variable + offset: int + lifetime: Tuple[int, int] + + # noinspection PyShadowingNames + def __init__(self, variable: Variable, offset: int, lifetime: Tuple[int, int]): + self.names = [] + self.variable = variable + self.offset = offset + self.lifetime = lifetime + + @property + def size(self): + return self.variable.size + + @property + def top(self): + return f"{self.lifetime[0] * UNIT_HEIGHT}px" + + @property + def height(self): + return f"{(self.lifetime[1] - self.lifetime[0]) * UNIT_HEIGHT + 1}px" + + @property + def left(self): + return f"{self.offset * 100 / total_size}%" + + @property + def width(self): + return f"calc({self.size * 100 / total_size}% + 1px)" + + # noinspection PyMethodMayBeStatic + def generate_html(self): + return f"""
+

{", ".join(self.names)}

+
""" + + html = """ + + + + +
+

Memory Allocation Visualization

+
+

Total allocation size: """ + str(total_size * 4) + """[byte]

+

# of allocated variables: """ + str(len(layout)) + """

+
+
+

縦軸:時間経過(上から下へ)

+

横軸:メモリアドレス

+

各要素はカーソルホバーで詳細が見られます。

+
+
+
+""" + + for v1 in variables: + v2 = v1 + while "inplace_src" in v2.parameters: + v2 = v2.parameters["inplace_src"] + + if v2 not in rendering_dict: + rendering_dict[v2] = RenderingInfo(v2, offsets[v2], lifetime[v2]) + + rendering_dict[v2].names.append(v1.name) + + for item in rendering_dict.values(): + html += item.generate_html() + + html += """ +
+ + +""" + + with open('memory_visualize.html', "w+") as f: + f.write(html) diff --git a/src/graph_transpiler/webdnn/backend/webgpu/injector.py b/src/graph_transpiler/webdnn/backend/code_generator/injector.py similarity index 75% rename from src/graph_transpiler/webdnn/backend/webgpu/injector.py rename to src/graph_transpiler/webdnn/backend/code_generator/injector.py index f03c4632d..22ff54654 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/injector.py +++ b/src/graph_transpiler/webdnn/backend/code_generator/injector.py @@ -1,12 +1,13 @@ import re -from typing import List, Tuple, NamedTuple +from typing import List, Tuple -class Tag(NamedTuple): - original: str - name: str - args: List[str] - span: Tuple[int, int] +class Tag: + def __init__(self, original: str, name: str, args: List[str], span: Tuple[int, int]): + self.original = original + self.name = name + self.args = args + self.span = span _reg_tag = re.compile("%%([a-zA-Z0-9_]+)(?:\((.*?)\))?%%", re.MULTILINE) @@ -29,8 +30,6 @@ def inject(self, text: str) -> str: args = [] if ma.group(2) is None else list(map(str.strip, ma.group(2).split(","))) span = ma.span() - # FIXME: This noinspection comment is not required. It's maybe a PyCharm's Bug? - # noinspection PyArgumentList tag = Tag(original, name, args, span) tags.append(tag) pos = tag.span[1] diff --git a/src/graph_transpiler/webdnn/frontend/sub_rules/__init__.py b/src/graph_transpiler/webdnn/backend/code_generator/injectors/__init__.py similarity index 100% rename from src/graph_transpiler/webdnn/frontend/sub_rules/__init__.py rename to src/graph_transpiler/webdnn/backend/code_generator/injectors/__init__.py diff --git a/src/graph_transpiler/webdnn/backend/code_generator/injectors/buffer_injector.py b/src/graph_transpiler/webdnn/backend/code_generator/injectors/buffer_injector.py new file mode 100644 index 000000000..b41dad506 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/code_generator/injectors/buffer_injector.py @@ -0,0 +1,278 @@ +from typing import Dict, Union, Tuple, List, Any + +import numpy as np + +from webdnn.backend.code_generator.allocator import Allocation, BufferType +from webdnn.backend.code_generator.injector import Tag, Injector +from webdnn.graph.placeholder import Placeholder +from webdnn.util import console + +Content = Union[ + int, + float, + bytes, + Allocation, + Placeholder, + List[Union[int, Placeholder]], + List[Allocation] +] + + +def _flatten(l: List[Any]): + """ + _flatten([[1, 2], 3, [[4], 5, [[6, 7]]], [8, 9]] == [1, 2, 3, 4, 5, 6, 7, 8, 9] + """ + return sum([_flatten(v) if isinstance(v, list) else [v] for v in l], []) + + +class BufferInjector(Injector): + def __init__(self, + meta_name: str = "meta_buffer", + static_name: str = "static_buffer", + dynamic_name: str = "dynamic_buffer"): + + self.value_map = {} # type: Dict[str, Content] + self.offset_map = None # type: Dict[str, int] + self.buffer = None # type: bytes + self.meta_name = meta_name + self.static_name = static_name + self.dynamic_name = dynamic_name + self.unresolved_value_list = [] # type: List[Tuple[int, Placeholder]] + + def register(self, data: Dict[str, Content]): + if self.offset_map is not None: + raise ValueError("BufferInjector#register must be called before BufferInjector#inject is called.") + + self.value_map.update(data) + + def inject_tag(self, tag: Tag): + """ + Inject BufferInjector Tag. Supported tags are as follows. + + `%%META_BUFFER%%` : get name of meta buffer: + + buffer_injector = BufferInjector() + buffer_injector.inject('std::string meta_buffer_name = "%%META_BUFFER%%";') + + # >> 'std::string meta_buffer_name = "meta_buffer";' + + `%%STATIC_BUFFER%%` : get name of static buffer: + + buffer_injector = BufferInjector() + buffer_injector.inject('std::string static_buffer_name = "%%STATIC_BUFFER%%";') + + # >> 'std::string static_buffer_name = "static_buffer";' + + `%%DYNAMIC_BUFFER%%` : get name of dynamic buffer: + + buffer_injector = BufferInjector() + buffer_injector.inject('std::string dynamic_buffer_name = "%%DYNAMIC_NAME%%";') + + # >> 'std::string dynamic_buffer_name = "dynamic_buffer";' + + `%%LOAD_BUFFER(key)%%` : load buffer value: + + buffer_injector = BufferInjector() + buffer_injector.register({ + "op_X": memory_layout[op_X], # dynamic variable allocation + "op_W": memory_layout[op_W], # static variable allocation + "op_N_B": 2 # scalar value + "op_Bs": [ # list of variable allocations + memory_layout[op_B1], + memory_layout[op_B2] + ] + }) + + buffer_injector.inject(''' + float *X = %%LOAD_BUFFER(op_X)%%; + const float *W = %%LOAD_BUFFER(op_W)%%; + const int N = %%LOAD_BUFFER(op_N_B)%%; + + for (int i = 0; i < N_B; i++) + { + const float B_i = %%LOAD_BUFFER(op_Bs, i)%% + } + ''') + + # >> ''' + # float *X = (dynamic_buffer + meta_buffer[0]); // For both cases, variable address is correctly resolved + # const float *W = (static_buffer + meta_buffer[1]); // based on offset value (meta_buffer[0] and meta_buffer[1]) + # const int N = meta_buffer[2] + # + # for (int i = 0; i < N_B; i++) + # { + # const float *B_i = (meta_buffer[3+2+(i)] ? static_buffer : dynamic_buffer) + meta_buffer[3 + (i)] + # } + # ''' + # + # int meta_buffer[7] = { + # op_X.offset, op_W.offset, 2 + # op_B1.offset, op_B2.offset, + # 0, 1 // 0 means op_B1 is in dynamic_buffer, and 1 means op_B2 is in static_buffer + # }; + + """ + if self.offset_map is None: + self._generate_buffer() + + if tag.name == "META_BUFFER": + return self.meta_name + + elif tag.name == "STATIC_BUFFER": + return self.static_name + + elif tag.name == "DYNAMIC_BUFFER": + return self.dynamic_name + + elif tag.name == "LOAD_BUFFER": + if len(tag.args) == 0: + raise ValueError(f"[BufferInjector] Requires least 1 arguments: {tag.original}") + + key = tag.args[0] + if key not in self.value_map: + raise KeyError(f"[BufferInjector] Key '{key}' is not registered in BufferInjector.") + + value = self.value_map[key] + + if isinstance(value, Allocation): + if len(tag.args) != 1: + console.warning(f"[BufferInjector] Requires just 1 arguments to inject allocation: {tag.original}") + + if value.buffer_type == BufferType.Static: + return f"({self.static_name} + {self.meta_name}[{self.offset_map[key]}])" + + else: + return f"({self.dynamic_name} + {self.meta_name}[{self.offset_map[key]}])" + + if isinstance(value, List): + + value = _flatten(value) + + if all([isinstance(p, int) or isinstance(p, Placeholder) for p in value]): + if len(tag.args) != 1: + console.warning(f"[BufferInjector] Requires just 1 arguments to inject a list of number: {tag.original}") + return f"(&({self.meta_name}[{self.offset_map[key]}]))" + + elif all([isinstance(a, Allocation) for a in value]): + if len(tag.args) < 2: + raise ValueError( + f"[BufferInjector] Second argument 'Index' is required to inject a list of allocations: {tag.original}") + + if len(tag.args) > 2: + console.warning(f"[BufferInjector] Requires only 2 arguments to inject a list of allocations: {tag.original}") + + index = tag.args[1] + return f"({self.meta_name}[{self.offset_map[key]}+{len(value)}+ ({index})] ? {self.static_name} : {self.dynamic_name})" + \ + f" + {self.meta_name}[{self.offset_map[key]} + ({index})]" + + if len(tag.args) > 2: + console.warning(f"[BufferInjector] Requires only 1 arguments to inject: {tag.original}") + + return f"{self.meta_name}[{self.offset_map[key]}]" + + else: + return tag.original + + def _generate_buffer(self) -> bytes: + if self.buffer: + return self.buffer + + offset_map = {} + buffer = bytes() + for key, value in self.value_map.items(): + offset_map[key] = len(buffer) // 4 # sizeof(int) + + if isinstance(value, int) or isinstance(value, np.int32) or isinstance(value, np.int64): + if isinstance(value, np.int64): + console.warning("[BufferInjector] np.int64 value is given to BufferInjector, and converted into int32 value.") + + buffer += np.array([value], dtype=np.int32).tobytes() + + elif isinstance(value, float) or isinstance(value, np.float32) or isinstance(value, np.float64): + if isinstance(value, np.float64): + console.warning("[BufferInjector] np.float64 value is given to BufferInjector, and converted into float32 value.") + + buffer += np.array([value], dtype=np.float32).tobytes() + + elif isinstance(value, bytes): + if len(value) % 4 != 0: + value += bytes(4 - (len(value) % 4)) + + buffer += value + + elif isinstance(value, Allocation): + if Placeholder.check_resolved(value.offset): + buffer += np.array([Placeholder.force_int(value.offset)], dtype=np.int32).tobytes() + + else: + self.unresolved_value_list.append((len(buffer) // 4, value.offset)) + buffer += bytes(4) # sizeof(int) + + elif isinstance(value, Placeholder): + if Placeholder.check_resolved(value): + buffer += np.array([Placeholder.force_int(value)], dtype=np.int32).tobytes() + + else: + self.unresolved_value_list.append((len(buffer) // 4, value)) + buffer += bytes(4) # sizeof(int) + + elif isinstance(value, List): + value = _flatten(value) + + if all([isinstance(p, int) or isinstance(p, Placeholder) for p in value]): + for p in value: + if isinstance(p, int) or isinstance(p, Placeholder): + if Placeholder.check_resolved(p): + buffer += np.array([Placeholder.force_int(p)], dtype=np.int32).tobytes() + + else: + self.unresolved_value_list.append((len(buffer) // 4, p)) + buffer += bytes(4) # sizeof(int) + + elif all([isinstance(a, Allocation) for a in value]): + """ + Input: + + value = [static1, dynamic2, ..., staticK] # allocations list whose length is K + tag = "float *x_i = %%LOAD_BUFFER(xs, i)%%" + + 1. pack offset of each allocation into meta buffer like single allocation: + + meta_buffer[M + 0] = static1.offset + meta_buffer[M + 1] = dynamic2.offset // In compile-time, this value is 0 + ... + meta_buffer[M + K-1] = staticK.offset + + 2. pack buffer type flag: + + meta_buffer[M + K + 0] = 1 // 1 means that static1 is in STATIC buffer + meta_buffer[M + K + 1] = 0 // 0 means that dynamic2 is in DYNAMIC buffer + ... + meta_buffer[M + K + K-1] = 0 + + 3. generate text: + + >> "float *x_i = (meta_buffer[M+K+ (i)] ? static_buffer : dynamic_buffer)[ meta_buffer[M + (i)] ]" + + """ + + for allocation in value: # type: Allocation + if Placeholder.check_resolved(allocation.offset): + buffer += np.array([Placeholder.force_int(allocation.offset)], dtype=np.int32).tobytes() + + else: + self.unresolved_value_list.append((len(buffer) // 4, allocation.offset)) + buffer += bytes(4) # sizeof(int) + + buffer += np.array([1 if allocation.buffer_type == BufferType.Static else 0 for allocation in value], + dtype=np.int32).tobytes() + + else: + raise TypeError( + "[BufferInjector] Only int, float, bytes, allocation, placeholder and list of placeholders is supported for injection. " + + f"'{key}' is {value}, whose type is {type(value)}.") + + self.offset_map = offset_map + self.buffer = buffer + + return self.buffer diff --git a/src/graph_transpiler/webdnn/backend/webgpu/injectors/inline_injector.py b/src/graph_transpiler/webdnn/backend/code_generator/injectors/inline_injector.py similarity index 92% rename from src/graph_transpiler/webdnn/backend/webgpu/injectors/inline_injector.py rename to src/graph_transpiler/webdnn/backend/code_generator/injectors/inline_injector.py index 32fe8b988..d314b0dc9 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/injectors/inline_injector.py +++ b/src/graph_transpiler/webdnn/backend/code_generator/injectors/inline_injector.py @@ -1,7 +1,7 @@ from typing import Callable +from webdnn.backend.code_generator.injector import Injector, Tag from webdnn.backend.webgpu.attributes.inline_inject import PostInlineInplace -from webdnn.backend.webgpu.injector import Injector, Tag from webdnn.graph import traverse from webdnn.graph.operator import Operator diff --git a/src/graph_transpiler/webdnn/backend/webgpu/injectors/kernel_name_injector.py b/src/graph_transpiler/webdnn/backend/code_generator/injectors/kernel_name_injector.py similarity index 84% rename from src/graph_transpiler/webdnn/backend/webgpu/injectors/kernel_name_injector.py rename to src/graph_transpiler/webdnn/backend/code_generator/injectors/kernel_name_injector.py index 84a39228c..77387263c 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/injectors/kernel_name_injector.py +++ b/src/graph_transpiler/webdnn/backend/code_generator/injectors/kernel_name_injector.py @@ -1,18 +1,15 @@ import hashlib from typing import Union -from webdnn.backend.webgpu.injector import Tag, Injector +from webdnn.backend.code_generator.injector import Tag, Injector from webdnn.graph.operator import Operator -def _add_canonical_suffix(base_name: str, source: str): - return - - class KernelNameInjector(Injector): def __init__(self, base_name: Union[str, Operator]): self.base_name = base_name.__class__.__name__.lower() if isinstance(base_name, Operator) else base_name self.name = base_name + self._text = "" def inject(self, text: str) -> str: self._text = text diff --git a/src/graph_transpiler/webdnn/backend/fallback/allocator.py b/src/graph_transpiler/webdnn/backend/fallback/allocator.py deleted file mode 100644 index 5b231abb5..000000000 --- a/src/graph_transpiler/webdnn/backend/fallback/allocator.py +++ /dev/null @@ -1,115 +0,0 @@ -from typing import Dict, Tuple, List, Set - -import numpy as np - -from webdnn.backend.interface.memory_layout import IMemoryLayout, IAllocation -from webdnn.graph import traverse -from webdnn.graph.graph import Graph -from webdnn.graph.variable import Variable -from webdnn.graph.variables.attributes.constant import Constant -from webdnn.graph.variables.constant_variable import ConstantVariable -from webdnn.util import json - - -class Allocation(json.SerializableMixin, IAllocation): - variable: Variable - offset: int - - def __init__(self, - variable: Variable, - offset: int): - self.variable = variable - self.offset = offset - - @property - def size(self) -> int: - return self.variable.size - - def _to_serializable_(self): - return { - "name": self.variable.parameters["name"], - "offset": self.offset, - "size": self.size - } - - -class MemoryLayout(json.SerializableMixin, IMemoryLayout): - size: int - __dict__: Dict[Variable, Allocation] - - def __init__(self): - self.__dict__ = {} - - def _to_serializable_(self): - return { - "total_size": self.size, - "allocation": {a.variable.parameters["name"]: a for _, a in self.__dict__.items()} - } - - def __getitem__(self, v: Variable): - return self.__dict__[v] - - def __contains__(self, v): - return v in self.__dict__ - - def append(self, variable: Variable, offset: int = -1): - if offset == -1: - offset = self.size - - self.__dict__[variable] = Allocation(variable, offset) - - @property - def size(self) -> int: - size = 0 - for v, a in self.__dict__.items(): - size = max(a.offset, size) + a.size - - return size - - -class Allocator: - layout: MemoryLayout - - @classmethod - def allocate(cls, graph: Graph) -> Tuple[MemoryLayout, MemoryLayout, np.array]: - variables = set(traverse.listup_variables(graph)) - for i, v in enumerate(variables): - v.parameters["name"] = f"v{i}" - constants = set(traverse.filter_nodes(variables, Constant)) # type: Set[ConstantVariable] - variables = variables.difference(constants) - - variables = list(variables) - constants = list(constants) - - constants_layout, data = cls.allocate_constants(constants) - variables_layout = cls.allocate_variables(variables) - return variables_layout, constants_layout, data - - @classmethod - def allocate_constants(cls, constants: List[ConstantVariable]) -> Tuple[MemoryLayout, np.ndarray]: - layout = MemoryLayout() - - for constant in constants: - if constant in layout: - continue - - layout.append(constant) - - buffer = np.zeros(layout.size, dtype=np.float32) - for constant in constants: - allocation = layout[constant] - buffer[allocation.offset:allocation.offset + allocation.size] = constant.data.flatten() - - return layout, buffer - - @classmethod - def allocate_variables(cls, variables: List[Variable]) -> MemoryLayout: - layout = MemoryLayout() - - for variable in variables: - if variable in layout: - continue - - layout.append(variable) - - return layout diff --git a/src/graph_transpiler/webdnn/backend/fallback/generator.py b/src/graph_transpiler/webdnn/backend/fallback/generator.py index e8b12afa6..a0ce7dbaa 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/generator.py +++ b/src/graph_transpiler/webdnn/backend/fallback/generator.py @@ -8,38 +8,61 @@ """ import os import os.path as path -from typing import List -from webdnn.backend.fallback.allocator import Allocator, MemoryLayout +from webdnn.backend.code_generator.allocator import Allocator from webdnn.backend.fallback.graph_descriptor import GraphDescriptor from webdnn.backend.fallback.kernel import Kernel from webdnn.backend.fallback.kernels.average_pooling_2d import average_pooling_2d from webdnn.backend.fallback.kernels.axiswise_bias import axiswise_bias from webdnn.backend.fallback.kernels.axiswise_scale import axiswise_scale +from webdnn.backend.fallback.kernels.clipped_relu import clipped_relu from webdnn.backend.fallback.kernels.concat import concat from webdnn.backend.fallback.kernels.convolution_2d import convolution_2d from webdnn.backend.fallback.kernels.elementwise_sum import elementwise_sum +from webdnn.backend.fallback.kernels.elu import elu from webdnn.backend.fallback.kernels.flatten import flatten +from webdnn.backend.fallback.kernels.hard_sigmoid import hard_sigmoid +from webdnn.backend.fallback.kernels.leaky_relu import leaky_relu from webdnn.backend.fallback.kernels.linear import linear from webdnn.backend.fallback.kernels.local_response_normalization import local_response_normalization from webdnn.backend.fallback.kernels.max_pooling_2d import max_pooling_2d +from webdnn.backend.fallback.kernels.reinterpret_axis import reinterpret_axis from webdnn.backend.fallback.kernels.relu import relu +from webdnn.backend.fallback.kernels.reshape import reshape +from webdnn.backend.fallback.kernels.scalar_affine import scalar_affine +from webdnn.backend.fallback.kernels.sigmoid import sigmoid +from webdnn.backend.fallback.kernels.softmax import softmax +from webdnn.backend.fallback.kernels.softplus import softplus +from webdnn.backend.fallback.kernels.softsign import softsign +from webdnn.backend.fallback.kernels.tanh import tanh +from webdnn.backend.interface.generator import DescriptorGenerator from webdnn.backend.interface.graph_descriptor import IGraphExecutionData from webdnn.encoder.constant_encoder import ConstantEncoder -from webdnn.graph import traverse from webdnn.graph.graph import Graph from webdnn.graph.operators.average_pooling_2d import AveragePooling2D from webdnn.graph.operators.axiswise_bias import AxiswiseBias from webdnn.graph.operators.axiswise_scale import AxiswiseScale +from webdnn.graph.operators.clipped_relu import ClippedRelu from webdnn.graph.operators.concat import Concat from webdnn.graph.operators.convolution2d import Convolution2D from webdnn.graph.operators.elementwise_sum import ElementwiseSum +from webdnn.graph.operators.elu import Elu from webdnn.graph.operators.flatten import Flatten +from webdnn.graph.operators.hard_sigmoid import HardSigmoid +from webdnn.graph.operators.leaky_relu import LeakyRelu from webdnn.graph.operators.linear import Linear from webdnn.graph.operators.local_response_normalization import LocalResponseNormalization from webdnn.graph.operators.max_pooling_2d import MaxPooling2D +from webdnn.graph.operators.reinterpret_axis import ReinterpretAxis from webdnn.graph.operators.relu import Relu -from webdnn.util import flags +from webdnn.graph.operators.reshape import Reshape +from webdnn.graph.operators.scalar_affine import ScalarAffine +from webdnn.graph.operators.sigmoid import Sigmoid +from webdnn.graph.operators.softmax import Softmax +from webdnn.graph.operators.softplus import Softplus +from webdnn.graph.operators.softsign import Softsign +from webdnn.graph.operators.tanh import Tanh +from webdnn.util import console from webdnn.util.json import json @@ -57,73 +80,62 @@ def save(self, dirname: str): with open(path.join(dirname, "graph_{}.json".format(self.backend_suffix)), "w") as f: json.dump(self.descriptor, f, indent=2) + with open(path.join(dirname, "kernels_{}.js".format(self.backend_suffix)), "w") as f: + f.write(self.descriptor.concat_kernel_sources()) + with open(path.join(dirname, "weight_{}.bin".format(self.backend_suffix)), "wb") as f: f.write(self.constants) -def generate(graph: Graph, constant_encoder_name: str = None) -> GraphExecutionData: - variables_layout, constants_layout, constants_data = Allocator.allocate(graph) - if flags.DEBUG: - print(f"[GraphDescriptorGeneratorFallback] constants_layout total size: {constants_data.size} * sizeof(float)") - print( - f"[GraphDescriptorGeneratorFallback] variables_layout total size: {variables_layout.size} * sizeof(float)") - constant_encoder = ConstantEncoder.get_encoder(constant_encoder_name) - constants_bytes = constant_encoder.encode(constants_layout, constants_data) - if flags.DEBUG: - print(f"[GraphDescriptorGeneratorFallback] constants encoded size: {len(constants_bytes)}") - - kernels = generate_kernels(graph, constants_layout, variables_layout) - - descriptor = GraphDescriptor( - kernels=kernels, - constants_layout=constants_layout, - variables_layout=variables_layout, - inputs=graph.inputs, - outputs=graph.outputs, - constants_encoding=constant_encoder.name, - licenses=graph.licenses) - - return GraphExecutionData(descriptor, constants_bytes) - - -# noinspection PyUnusedLocal -def generate_kernels(graph: Graph, constants_layout: MemoryLayout, variables_layout: MemoryLayout) -> List[Kernel]: - kernels: List[Kernel] = [] - for op in traverse.listup_operators(graph): - if isinstance(op, Linear): - kernels += linear(op) - - elif isinstance(op, AxiswiseBias): - kernels += axiswise_bias(op) - - elif isinstance(op, AxiswiseScale): - kernels += axiswise_scale(op) - - elif isinstance(op, Convolution2D): - kernels += convolution_2d(op) - - elif isinstance(op, MaxPooling2D): - kernels += max_pooling_2d(op) - - elif isinstance(op, AveragePooling2D): - kernels += average_pooling_2d(op) - - elif isinstance(op, ElementwiseSum): - kernels += elementwise_sum(op) - - elif isinstance(op, Flatten): - kernels += flatten(op) - - elif isinstance(op, Relu): - kernels += relu(op) - - elif isinstance(op, Concat): - kernels += concat(op) - - elif isinstance(op, LocalResponseNormalization): - kernels += local_response_normalization(op) - - else: - raise NotImplementedError(f"{op} is Unknown for Fallback Descriptor Generator") - - return kernels +class FallbackDescriptorGenerator(DescriptorGenerator[Kernel, GraphExecutionData]): + @classmethod + def generate(cls, graph: Graph, **kwargs): + memory_layout = Allocator.allocate(graph) + + console.debug(f"[FallbackDescriptorGenerator] memory_layout total size: {memory_layout.total_size * 4}") + console.debug(f"[FallbackDescriptorGenerator] memory_layout static size: {memory_layout.static_size * 4}") + console.debug(f"[FallbackDescriptorGenerator] memory_layout dynamic size: {memory_layout.dynamic_size * 4}") + + constant_encoder = ConstantEncoder.get_encoder(kwargs.get("constant_encoder_name", None)) + constants_bytes = constant_encoder.encode(memory_layout) + + console.debug(f"[FallbackDescriptorGenerator] constants encoded size: {len(constants_bytes)}") + + descriptor = GraphDescriptor( + kernels=cls.generate_kernels(graph, memory_layout), + memory_layout=memory_layout, + inputs=graph.inputs, + outputs=graph.outputs, + constants_encoding=constant_encoder.name, + licenses=graph.licenses) + + return GraphExecutionData(descriptor, constants_bytes) + + +def generate(graph: Graph, **kwargs): + return FallbackDescriptorGenerator.generate(graph, **kwargs) + + +FallbackDescriptorGenerator.register_handler(AveragePooling2D)(average_pooling_2d) +FallbackDescriptorGenerator.register_handler(AxiswiseBias)(axiswise_bias) +FallbackDescriptorGenerator.register_handler(AxiswiseScale)(axiswise_scale) +FallbackDescriptorGenerator.register_handler(Concat)(concat) +FallbackDescriptorGenerator.register_handler(ClippedRelu)(clipped_relu) +FallbackDescriptorGenerator.register_handler(Convolution2D)(convolution_2d) +FallbackDescriptorGenerator.register_handler(ElementwiseSum)(elementwise_sum) +FallbackDescriptorGenerator.register_handler(Elu)(elu) +FallbackDescriptorGenerator.register_handler(Flatten)(flatten) +FallbackDescriptorGenerator.register_handler(HardSigmoid)(hard_sigmoid) +FallbackDescriptorGenerator.register_handler(LeakyRelu)(leaky_relu) +FallbackDescriptorGenerator.register_handler(Linear)(linear) +FallbackDescriptorGenerator.register_handler(LocalResponseNormalization)(local_response_normalization) +FallbackDescriptorGenerator.register_handler(MaxPooling2D)(max_pooling_2d) +FallbackDescriptorGenerator.register_handler(ScalarAffine)(scalar_affine) +FallbackDescriptorGenerator.register_handler(Sigmoid)(sigmoid) +FallbackDescriptorGenerator.register_handler(Softmax)(softmax) +FallbackDescriptorGenerator.register_handler(Softplus)(softplus) +FallbackDescriptorGenerator.register_handler(Softsign)(softsign) +FallbackDescriptorGenerator.register_handler(ReinterpretAxis)(reinterpret_axis) +FallbackDescriptorGenerator.register_handler(Relu)(relu) +FallbackDescriptorGenerator.register_handler(Reshape)(reshape) +FallbackDescriptorGenerator.register_handler(Tanh)(tanh) diff --git a/src/graph_transpiler/webdnn/backend/fallback/graph_descriptor.py b/src/graph_transpiler/webdnn/backend/fallback/graph_descriptor.py index 2f702bf7f..b2afa75d4 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/graph_descriptor.py +++ b/src/graph_transpiler/webdnn/backend/fallback/graph_descriptor.py @@ -1,10 +1,11 @@ from collections import OrderedDict -from typing import Iterable, Dict +from typing import Iterable, Dict, Set -from webdnn.backend.fallback.allocator import MemoryLayout +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.backend.interface.graph_descriptor import IGraphDescriptor from webdnn.graph import traverse +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable from webdnn.graph.variables.attributes.constant import Constant from webdnn.util import json @@ -20,8 +21,7 @@ class GraphDescriptor(json.SerializableMixin, IGraphDescriptor): kernels: Iterable[Kernel] - constants_layout: MemoryLayout - variables_layout: MemoryLayout + memory_layout: MemoryLayout inputs: Iterable[Variable] outputs: Iterable[Variable] constants_encoding: str @@ -29,15 +29,13 @@ class GraphDescriptor(json.SerializableMixin, IGraphDescriptor): def __init__(self, kernels: Iterable[Kernel], - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, + memory_layout: MemoryLayout, inputs: Iterable[Variable], outputs: Iterable[Variable], constants_encoding: str, licenses: Dict[str, str]): self.kernels = kernels - self.constants_layout = constants_layout - self.variables_layout = variables_layout + self.memory_layout = memory_layout self.inputs = inputs self.outputs = outputs self.constants_encoding = constants_encoding @@ -59,13 +57,29 @@ def concat_kernel_sources(self): return combined_source + def get_all_placeholders(self): + placeholders_set = set() # type: Set[Placeholder] + + for kernel in self.kernels: + for value in kernel.exec_info.call_option.values(): + if Placeholder.check_resolved(value): + continue + + placeholders_set.update(value.get_depend_placeholders()) + + placeholders = {p.label: None for p in placeholders_set} + + return placeholders + def _to_serializable_(self): + placeholders = self.get_all_placeholders() + return { "kernel_source": self.concat_kernel_sources(), "exec_infos": [kernel.exec_info for kernel in self.kernels], - "weight_allocation": self.constants_layout, "weight_encoding": self.constants_encoding, - "variable_allocation": self.variables_layout, + "memory_layout": self.memory_layout, + "placeholders": placeholders, "inputs": [v.parameters["name"] for v in self.inputs if not traverse.check_attribute_match(v, Constant)], "outputs": [v.parameters["name"] for v in self.outputs], "licenses": self.licenses diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernel.py b/src/graph_transpiler/webdnn/backend/fallback/kernel.py index d60477661..0cb544e17 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernel.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernel.py @@ -1,33 +1,30 @@ from typing import Dict, Iterable +from webdnn.graph.variable import Variable from webdnn.util import json class KernelExecutionInfo(json.SerializableMixin): entry_func_name: str - inputs: Iterable[str] - outputs: Iterable[str] - weights: Iterable[str] + inputs: Iterable[Variable] + outputs: Iterable[Variable] call_option: Dict[str, object] def __init__(self, entry_func_name: str, - inputs: Iterable[str], - outputs: Iterable[str], - weights: Iterable[str], + inputs: Iterable[Variable], + outputs: Iterable[Variable], call_option: Dict[str, object]): self.entry_func_name = entry_func_name self.inputs = inputs self.outputs = outputs - self.weights = weights self.call_option = call_option def _to_serializable_(self): return { "entry_func_name": self.entry_func_name, - "inputs": self.inputs, - "outputs": self.outputs, - "weights": self.weights, + "inputs": [v.name for v in self.inputs], + "outputs": [v.name for v in self.outputs], "call_option": self.call_option } @@ -39,15 +36,13 @@ class Kernel: def __init__(self, func_sources: Dict[str, str], entry_func_name: str, - inputs: Iterable[str], - outputs: Iterable[str], - weights: Iterable[str], + inputs: Iterable[Variable], + outputs: Iterable[Variable], call_option: Dict[str, object]): self.func_sources = func_sources self.exec_info = KernelExecutionInfo( entry_func_name=entry_func_name, inputs=inputs, outputs=outputs, - weights=weights, call_option=call_option ) diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/average_pooling_2d.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/average_pooling_2d.py index 5c3e43f19..a6f17a8ee 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/average_pooling_2d.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/average_pooling_2d.py @@ -1,5 +1,6 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.backend.fallback.kernels.util import calculate_stride from webdnn.graph.axis import Axis @@ -8,7 +9,7 @@ # x: (batch_size, h, w, in_size), w: (kh, kw, in_size, out_size), y: (batch_size, oh, ow, out_size) C-order # EcmaScript3 to support older browsers source = """ -average_pooling_2d: function(input_arrays, output_arrays, param_arrays, option) { +average_pooling_2d: function(input_arrays, output_arrays, option) { var x = input_arrays[0]; var y = output_arrays[0]; var n = option.n | 0; @@ -62,16 +63,16 @@ def calculate_all_strides(var): return [calculate_stride(var, axis) for axis in [Axis.N, Axis.H, Axis.W, Axis.C]] -def average_pooling_2d(op: AveragePooling2D) -> List[Kernel]: +# noinspection PyUnusedLocal +def average_pooling_2d(op: AveragePooling2D, memory_layout: MemoryLayout) -> List[Kernel]: x = op.inputs["x"] y = op.outputs["y"] kernel = Kernel( {"average_pooling_2d": source}, "average_pooling_2d", - inputs=[x.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[], + inputs=[x], + outputs=[y], call_option={"in_spatial": [x.shape_dict[Axis.H], x.shape_dict[Axis.W]], "n": x.shape_dict[Axis.N], "out_size": y.shape_dict[Axis.C], diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/axiswise_bias.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/axiswise_bias.py index ecf484f63..357a5bfde 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/axiswise_bias.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/axiswise_bias.py @@ -2,17 +2,19 @@ import numpy as np +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.graph.operators.axiswise_bias import AxiswiseBias # assume (batch_size, in_size) * (in_size, out_size) = (batch_size, out_size), C-order # EcmaScript3 to support older browsers +from webdnn.util.misc import mul source = """ -axiswise_bias: function(input_arrays, output_arrays, param_arrays, option) { +axiswise_bias: function(input_arrays, output_arrays, option) { var x = input_arrays[0]; +var b = input_arrays[1]; var y = output_arrays[0]; -var b = param_arrays[0]; var n = option.n | 0; var axis_stride = option.axis_stride | 0; var axis_size = option.axis_size | 0; @@ -27,7 +29,8 @@ """ -def axiswise_bias(op: AxiswiseBias) -> List[Kernel]: +# noinspection PyUnusedLocal +def axiswise_bias(op: AxiswiseBias, memory_layout: MemoryLayout) -> List[Kernel]: # 該当軸のsize, strideを与える x = op.inputs["x"] b = op.inputs["b"] @@ -38,15 +41,13 @@ def axiswise_bias(op: AxiswiseBias) -> List[Kernel]: axis_size = x.shape[axis_pos] assert axis_size == b.size - # noinspection PyTypeChecker - axis_stride = int(np.prod(x.shape[axis_pos + 1:])) # NCHWでaxis=Cなら、size(H)*size(W), np.prod([])==1.0 + axis_stride = mul(x.shape[axis_pos + 1:]) kernel = Kernel( {"axiswise_bias": source}, "axiswise_bias", - inputs=[x.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[b.parameters["name"]], + inputs=[x, b], + outputs=[y], call_option={"n": x.size, "axis_stride": axis_stride, "axis_size": axis_size} diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/axiswise_scale.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/axiswise_scale.py index 1daf87703..8410cb265 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/axiswise_scale.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/axiswise_scale.py @@ -2,24 +2,26 @@ import numpy as np +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.graph.operators.axiswise_scale import AxiswiseScale # assume (batch_size, in_size) * (in_size, out_size) = (batch_size, out_size), C-order # EcmaScript3 to support older browsers +from webdnn.util.misc import mul source = """ -axiswise_scale: function(input_arrays, output_arrays, param_arrays, option) { +axiswise_scale: function(input_arrays, output_arrays, option) { var x = input_arrays[0]; +var s = input_arrays[1]; var y = output_arrays[0]; -var b = param_arrays[0]; var n = option.n | 0; var axis_stride = option.axis_stride | 0; var axis_size = option.axis_size | 0; for (var i = 0; i < n; i++) { var ch = (i / axis_stride | 0) % axis_size; - y[i] = x[i] * b[ch]; + y[i] = x[i] * s[ch]; } }, @@ -27,24 +29,25 @@ """ -def axiswise_scale(op: AxiswiseScale) -> List[Kernel]: +# noinspection PyUnusedLocal +def axiswise_scale(op: AxiswiseScale, memory_layout: MemoryLayout) -> List[Kernel]: # 該当軸のsize, strideを与える x = op.inputs["x"] - b = op.inputs["s"] + s = op.inputs["s"] y = op.outputs["y"] - assert b.ndim == 1 + assert s.ndim == 1 axis_pos = x.order.axes_dict[op.parameters["axis"]] # NCHWでaxis=Cなら、1 axis_size = x.shape[axis_pos] - assert axis_size == b.size - axis_stride = int(np.prod(x.shape[axis_pos + 1:])) # NCHWでaxis=Cなら、size(H)*size(W), np.prod([])==1.0 + assert axis_size == s.size + + axis_stride = mul(x.shape[axis_pos + 1:]) kernel = Kernel( {"axiswise_scale": source}, "axiswise_scale", - inputs=[x.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[b.parameters["name"]], + inputs=[x, s], + outputs=[y], call_option={"n": x.size, "axis_stride": axis_stride, "axis_size": axis_size} diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/clipped_relu.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/clipped_relu.py new file mode 100644 index 000000000..7f6452fd6 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/clipped_relu.py @@ -0,0 +1,39 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.clipped_relu import ClippedRelu + +# EcmaScript3 to support older browsers + +source = """ +clipped_relu: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; +var cap = option.cap; + +for (var i = 0; i < length; i++) { + var val = x[i]; + y[i] = val >= 0.0 ? (val < cap ? val : cap) : 0.0; +} + +}, + +""" + + +# noinspection PyUnresolvedReferences +def clipped_relu(op: ClippedRelu, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + kernel = Kernel( + {"clipped_relu": source}, + "clipped_relu", + inputs=[x], + outputs=[y], + call_option={"length": x.size, "cap": op.parameters["cap"]} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/concat.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/concat.py index ddcdee7a0..c2c796216 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/concat.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/concat.py @@ -1,10 +1,11 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.graph.operators.concat import Concat source = """ -concat: function(input_arrays, output_arrays, param_arrays, option) { +concat: function(input_arrays, output_arrays, option) { var xs = input_arrays; var y = output_arrays[0]; var shapes = option.x_shapes; @@ -67,7 +68,8 @@ """ -def concat(op: Concat) -> List[Kernel]: +# noinspection PyUnusedLocal +def concat(op: Concat, memory_layout: MemoryLayout) -> List[Kernel]: xs = [op.inputs[f"x{i}"] for i in range(len(op.inputs))] y = op.outputs["y"] target_axis = op.axis @@ -96,9 +98,8 @@ def concat(op: Concat) -> List[Kernel]: kernel = Kernel( {"concat": source}, "concat", - inputs=[x.parameters["name"] for x in xs], - outputs=[y.parameters["name"]], - weights=[], + inputs=xs, + outputs=[y], call_option={"x_shapes": x_shapes, "x_strides": x_strides, "x_offsets": x_offsets} diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/convolution_2d.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/convolution_2d.py index 14a28da4a..2b3b230ca 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/convolution_2d.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/convolution_2d.py @@ -1,5 +1,6 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.backend.fallback.kernels.util import calculate_stride from webdnn.graph.axis import Axis @@ -8,10 +9,10 @@ # x: (batch_size, h, w, in_size), w: (kh, kw, in_size, out_size), y: (batch_size, oh, ow, out_size) C-order # EcmaScript3 to support older browsers source = """ -convolution_2d: function(input_arrays, output_arrays, param_arrays, option) { +convolution_2d: function(input_arrays, output_arrays, option) { var x = input_arrays[0]; +var w = input_arrays[1]; var y = output_arrays[0]; -var w = param_arrays[0]; var n = option.n | 0; var in_spatial = option.in_spatial; var out_spatial = option.out_spatial; @@ -20,6 +21,7 @@ var padding = option.padding; var stride = option.stride; var ksize = option.ksize; +var dilation_rate = option.dilation_rate; var strides_x = option.strides_x; var strides_w = option.strides_w; var strides_y = option.strides_y; @@ -52,7 +54,10 @@ for (var ky = 0; ky < ksize[0]; ky++) { for (var kx = 0; kx < ksize[1]; kx++) { for (var ic = 0; ic < in_size; ic++) { - sum += get_x(batch, oy * stride[0] + ky, ox * stride[1] + kx, ic) * get_w(ky, kx, ic, oc); + sum += get_x(batch, oy * stride[0] + ky * dilation_rate[0], + ox * stride[1] + kx * dilation_rate[1], + ic) * + get_w(ky, kx, ic, oc); } } } @@ -71,7 +76,8 @@ def calculate_all_strides(var): return [calculate_stride(var, axis) for axis in [Axis.N, Axis.H, Axis.W, Axis.C]] -def convolution_2d(op: Convolution2D) -> List[Kernel]: +# noinspection PyUnusedLocal +def convolution_2d(op: Convolution2D, memory_layout: MemoryLayout) -> List[Kernel]: x = op.inputs["x"] w = op.inputs["w"] y = op.outputs["y"] @@ -79,9 +85,8 @@ def convolution_2d(op: Convolution2D) -> List[Kernel]: kernel = Kernel( {"convolution_2d": source}, "convolution_2d", - inputs=[x.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[w.parameters["name"]], + inputs=[x, w], + outputs=[y], call_option={"in_spatial": [x.shape_dict[Axis.H], x.shape_dict[Axis.W]], "n": x.shape_dict[Axis.N], "out_size": y.shape_dict[Axis.C], @@ -92,7 +97,8 @@ def convolution_2d(op: Convolution2D) -> List[Kernel]: "strides_y": calculate_all_strides(y), "padding": op.padding, "stride": op.stride, - "ksize": op.ksize} + "ksize": op.ksize, + "dilation_rate": op.dilation_rate} ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/elementwise_sum.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/elementwise_sum.py index 714a59ce4..0dcc90813 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/elementwise_sum.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/elementwise_sum.py @@ -1,5 +1,6 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.graph.operators.elementwise_sum import ElementwiseSum @@ -7,7 +8,7 @@ # EcmaScript3 to support older browsers source = """ -elementwise_sum: function(input_arrays, output_arrays, param_arrays, option) { +elementwise_sum: function(input_arrays, output_arrays, option) { var x0 = input_arrays[0]; var x1 = input_arrays[1]; var y = output_arrays[0]; @@ -22,7 +23,8 @@ """ -def elementwise_sum(op: ElementwiseSum) -> List[Kernel]: +# noinspection PyUnusedLocal +def elementwise_sum(op: ElementwiseSum, memory_layout: MemoryLayout) -> List[Kernel]: assert len(op.inputs) == 2 x0 = op.inputs["x0"] x1 = op.inputs["x1"] @@ -33,9 +35,8 @@ def elementwise_sum(op: ElementwiseSum) -> List[Kernel]: kernel = Kernel( {"elementwise_sum": source}, "elementwise_sum", - inputs=[x0.parameters["name"], x1.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[], + inputs=[x0, x1], + outputs=[y], call_option={"length": x0.size} ) diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/elu.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/elu.py new file mode 100644 index 000000000..73483a9cd --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/elu.py @@ -0,0 +1,38 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.elu import Elu + +# EcmaScript3 to support older browsers + +source = """ +elu: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; + +for (var i = 0; i < length; i++) { + var val = x[i]; + y[i] = val < 0.0 ? (Math.exp(val) - 1.0) : val; +} + +}, + +""" + + +# noinspection PyUnresolvedReferences +def elu(op: Elu, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + kernel = Kernel( + {"elu": source}, + "elu", + inputs=[x], + outputs=[y], + call_option={"length": x.size} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/flatten.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/flatten.py index deaf47340..b4c69f8b9 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/flatten.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/flatten.py @@ -1,5 +1,6 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.graph.operators.flatten import Flatten from webdnn.graph.order import OrderNCHW, OrderNC, OrderNHWC @@ -8,7 +9,7 @@ # EcmaScript3 to support older browsers source = """ -flatten: function(input_arrays, output_arrays, param_arrays, option) { +flatten: function(input_arrays, output_arrays, option) { var x = input_arrays[0]; var y = output_arrays[0]; var length = option.length | 0; @@ -22,7 +23,8 @@ """ -def flatten(op: Flatten) -> List[Kernel]: +# noinspection PyUnusedLocal +def flatten(op: Flatten, memory_layout: MemoryLayout) -> List[Kernel]: # データ変換がない場合のみ現状サポート # 該当軸のsize, strideを与える x = op.inputs["x"] @@ -38,9 +40,8 @@ def flatten(op: Flatten) -> List[Kernel]: kernel = Kernel( {"flatten": source}, "flatten", - inputs=[x.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[], + inputs=[x], + outputs=[y], call_option={"length": x.size} ) diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/hard_sigmoid.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/hard_sigmoid.py new file mode 100644 index 000000000..35226804d --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/hard_sigmoid.py @@ -0,0 +1,45 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.hard_sigmoid import HardSigmoid + +# assume (batch_size, in_size) * (in_size, out_size) = (batch_size, out_size), C-order +# EcmaScript3 to support older browsers + +source = """ +hard_sigmoid: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; + +for (var i = 0; i < length; i++) { + var val = x[i]; + val = val * 0.2 + 0.5; + if (val < 0.0) { + val = 0.0; + } else if (val > 1.0) { + val = 1.0; + } + y[i] = val; +} + +}, + +""" + + +# noinspection PyUnresolvedReferences +def hard_sigmoid(op: HardSigmoid, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + kernel = Kernel( + {"hard_sigmoid": source}, + "hard_sigmoid", + inputs=[x], + outputs=[y], + call_option={"length": x.size} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/leaky_relu.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/leaky_relu.py new file mode 100644 index 000000000..7f5d8cda2 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/leaky_relu.py @@ -0,0 +1,39 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.leaky_relu import LeakyRelu + +# EcmaScript3 to support older browsers + +source = """ +leaky_relu: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; +var slope = option.slope; + +for (var i = 0; i < length; i++) { + var val = x[i]; + y[i] = val >= 0.0 ? val : val * slope; +} + +}, + +""" + + +# noinspection PyUnresolvedReferences +def leaky_relu(op: LeakyRelu, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + kernel = Kernel( + {"leaky_relu": source}, + "leaky_relu", + inputs=[x], + outputs=[y], + call_option={"length": x.size, "slope": op.parameters["slope"]} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/linear.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/linear.py index fe0583098..743bdadae 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/linear.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/linear.py @@ -1,5 +1,6 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.backend.fallback.kernels.util import calculate_stride from webdnn.graph.axis import Axis @@ -10,10 +11,10 @@ # EcmaScript3 to support older browsers source = """ -linear: function(input_arrays, output_arrays, param_arrays, option) { +linear: function(input_arrays, output_arrays, option) { var x = input_arrays[0]; +var w = input_arrays[1]; var y = output_arrays[0]; -var w = param_arrays[0]; var m = option.m | 0; var n = option.n | 0; var k = option.k | 0; @@ -38,7 +39,8 @@ """ -def linear(op: Linear) -> List[Kernel]: +# noinspection PyUnusedLocal +def linear(op: Linear, memory_layout: MemoryLayout) -> List[Kernel]: x = op.inputs["x"] w = op.inputs["w"] y = op.outputs["y"] @@ -101,9 +103,8 @@ def linear(op: Linear) -> List[Kernel]: kernel = Kernel( {"linear": source}, "linear", - inputs=[x.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[w.parameters["name"]], + inputs=[x, w], + outputs=[y], call_option={"m": m, "n": n, "k": k, diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/local_response_normalization.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/local_response_normalization.py index b1636ad66..7b7f69102 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/local_response_normalization.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/local_response_normalization.py @@ -1,5 +1,6 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.backend.fallback.kernels.util import calculate_stride from webdnn.graph.axis import Axis @@ -9,7 +10,7 @@ # EcmaScript3 to support older browsers source = """ -local_response_normalization: function(input_arrays, output_arrays, param_arrays, option) { +local_response_normalization: function(input_arrays, output_arrays, option) { var x = input_arrays[0]; var y = output_arrays[0]; var n = option.n | 0; @@ -68,16 +69,16 @@ def calculate_all_strides(var): return [calculate_stride(var, axis) for axis in [Axis.N, Axis.H, Axis.W, Axis.C]] -def local_response_normalization(op: LocalResponseNormalization) -> List[Kernel]: +# noinspection PyUnusedLocal +def local_response_normalization(op: LocalResponseNormalization, memory_layout: MemoryLayout) -> List[Kernel]: x = op.inputs["x"] y = op.outputs["y"] kernel = Kernel( {"local_response_normalization": source}, "local_response_normalization", - inputs=[x.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[], + inputs=[x], + outputs=[y], call_option={"out_spatial": [y.shape_dict[Axis.H], y.shape_dict[Axis.W]], "n": x.shape_dict[Axis.N], "out_size": y.shape_dict[Axis.C], diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/max_pooling_2d.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/max_pooling_2d.py index f4bdc487e..4d39f1399 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/max_pooling_2d.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/max_pooling_2d.py @@ -1,5 +1,6 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.backend.fallback.kernels.util import calculate_stride from webdnn.graph.axis import Axis @@ -9,7 +10,7 @@ # EcmaScript3 to support older browsers source = """ -max_pooling_2d: function(input_arrays, output_arrays, param_arrays, option) { +max_pooling_2d: function(input_arrays, output_arrays, option) { var x = input_arrays[0]; var y = output_arrays[0]; var n = option.n | 0; @@ -66,16 +67,16 @@ def calculate_all_strides(var): return [calculate_stride(var, axis) for axis in [Axis.N, Axis.H, Axis.W, Axis.C]] -def max_pooling_2d(op: MaxPooling2D) -> List[Kernel]: +# noinspection PyUnusedLocal +def max_pooling_2d(op: MaxPooling2D, memory_layout: MemoryLayout) -> List[Kernel]: x = op.inputs["x"] y = op.outputs["y"] kernel = Kernel( {"max_pooling_2d": source}, "max_pooling_2d", - inputs=[x.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[], + inputs=[x], + outputs=[y], call_option={"in_spatial": [x.shape_dict[Axis.H], x.shape_dict[Axis.W]], "n": x.shape_dict[Axis.N], "out_size": y.shape_dict[Axis.C], diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/reinterpret_axis.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/reinterpret_axis.py new file mode 100644 index 000000000..a4db895f5 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/reinterpret_axis.py @@ -0,0 +1,42 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.reinterpret_axis import ReinterpretAxis + +# EcmaScript3 to support older browsers +from webdnn.util.misc import mul + +source = """ +reinterpret_axis: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; + +for (var i = 0; i < length; i++) { + y[i] = x[i]; +} + +}, + +""" + + +# noinspection PyUnusedLocal +def reinterpret_axis(op: ReinterpretAxis, memory_layout: MemoryLayout) -> List[Kernel]: + # Operation without need for transposition is currently supported + x = op.inputs["x"] + y = op.outputs["y"] + + assert x.order == op.parameters["in_order"] + assert y.order == op.parameters["out_order"] + + kernel = Kernel( + {"reinterpret_axis": source}, + "reinterpret_axis", + inputs=[x], + outputs=[y], + call_option={"length": x.size} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/relu.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/relu.py index 2ece6e213..d4efb0bc7 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/relu.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/relu.py @@ -1,5 +1,6 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.fallback.kernel import Kernel from webdnn.graph.operators.relu import Relu @@ -7,7 +8,7 @@ # EcmaScript3 to support older browsers source = """ -relu: function(input_arrays, output_arrays, param_arrays, option) { +relu: function(input_arrays, output_arrays, option) { var x = input_arrays[0]; var y = output_arrays[0]; var length = option.length | 0; @@ -22,16 +23,15 @@ """ -def relu(op: Relu) -> List[Kernel]: +def relu(op: Relu, memory_layout: MemoryLayout) -> List[Kernel]: x = op.inputs["x"] y = op.outputs["y"] kernel = Kernel( {"relu": source}, "relu", - inputs=[x.parameters["name"]], - outputs=[y.parameters["name"]], - weights=[], + inputs=[x], + outputs=[y], call_option={"length": x.size} ) diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/reshape.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/reshape.py new file mode 100644 index 000000000..a182f0f56 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/reshape.py @@ -0,0 +1,43 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.reshape import Reshape + +# EcmaScript3 to support older browsers +from webdnn.util.misc import mul + +source = """ +reshape: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; + +for (var i = 0; i < length; i++) { + y[i] = x[i]; +} + +}, + +""" + + +# noinspection PyUnusedLocal +def reshape(op: Reshape, memory_layout: MemoryLayout) -> List[Kernel]: + # Operation without need for transposition is currently supported + x = op.inputs["x"] + y = op.outputs["y"] + + assert x.order == op.parameters["in_order"] + assert y.order == op.parameters["out_order"] + assert y.size == mul(op.parameters["out_shape"]) + + kernel = Kernel( + {"reshape": source}, + "reshape", + inputs=[x], + outputs=[y], + call_option={"length": x.size} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/scalar_affine.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/scalar_affine.py new file mode 100644 index 000000000..7553af76b --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/scalar_affine.py @@ -0,0 +1,43 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +# assume (batch_size, in_size) * (in_size, out_size) = (batch_size, out_size), C-order +# EcmaScript3 to support older browsers +from webdnn.graph.operators.scalar_affine import ScalarAffine + +source = """ +scalar_affine: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; +var s = option.s | 0; +var b = option.b | 0; + +for (var i = 0; i < length; i++) { + var val = x[i]; + y[i] = x[i] * s + b; +} + +}, + +""" + + +def scalar_affine(op: ScalarAffine, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + kernel = Kernel( + {"scalar_affine": source}, + "scalar_affine", + inputs=[x], + outputs=[y], + call_option={ + "length": x.size, + "s": op.scale, + "b": op.bias + } + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/sigmoid.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/sigmoid.py new file mode 100644 index 000000000..c5329694c --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/sigmoid.py @@ -0,0 +1,38 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.sigmoid import Sigmoid + +# EcmaScript3 to support older browsers + +source = """ +sigmoid: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; + +for (var i = 0; i < length; i++) { + var val = x[i]; + y[i] = 1.0 / (1.0 + Math.exp(-val)); +} + +}, + +""" + + +# noinspection PyUnresolvedReferences +def sigmoid(op: Sigmoid, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + kernel = Kernel( + {"sigmoid": source}, + "sigmoid", + inputs=[x], + outputs=[y], + call_option={"length": x.size} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/softmax.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/softmax.py new file mode 100644 index 000000000..af10ba31e --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/softmax.py @@ -0,0 +1,63 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.softmax import Softmax + +# EcmaScript3 to support older browsers + +source = """ +softmax: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var N = option.N | 0; +var C = option.C | 0; + +for (var n = 0; n < N; n++) { + var set_max = x[n * C]; + for (var c = 0; c < C; c++) { + var val = x[n * C + c]; + if (val > set_max) { + set_max = val; + } + } + + var sum_exp = 0.0; + for (var c = 0; c < C; c++) { + var val = x[n * C + c]; + var exp_x = Math.exp(val - set_max); + sum_exp += exp_x; + y[n * C + c] = exp_x; + } + + for (var c = 0; c < C; c++) { + y[n * C + c] /= sum_exp; + } +} +}, + +""" + + +# noinspection PyUnusedLocal +def softmax(op: Softmax, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + assert y.order == x.order + assert y.shape == x.shape + + axis = op.parameters["axis"] + assert axis == x.order.axes[-1], "[Fallback] Softmax supports only for aggregating last axis." + + kernel = Kernel( + {"softmax": source}, + "softmax", + inputs=[x], + outputs=[y], + call_option={ + "N": y.size // y.shape_dict[axis], + "C": y.shape_dict[axis]} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/softplus.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/softplus.py new file mode 100644 index 000000000..2a51b48ef --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/softplus.py @@ -0,0 +1,40 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.softplus import Softplus + +# EcmaScript3 to support older browsers + +source = """ +softplus: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; +var beta = option.beta; +var beta_inv = 1.0 / beta; + +for (var i = 0; i < length; i++) { + var val = x[i]; + y[i] = Math.log(1.0 + Math.exp(beta * val)) * beta_inv; +} + +}, + +""" + + +# noinspection PyUnresolvedReferences +def softplus(op: Softplus, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + kernel = Kernel( + {"softplus": source}, + "softplus", + inputs=[x], + outputs=[y], + call_option={"length": x.size, "beta": op.parameters["beta"]} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/softsign.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/softsign.py new file mode 100644 index 000000000..5028573d9 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/softsign.py @@ -0,0 +1,38 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.softsign import Softsign + +# EcmaScript3 to support older browsers + +source = """ +softsign: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; + +for (var i = 0; i < length; i++) { + var val = x[i]; + y[i] = val / (Math.abs(val) + 1.0); +} + +}, + +""" + + +# noinspection PyUnresolvedReferences +def softsign(op: Softsign, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + kernel = Kernel( + {"softsign": source}, + "softsign", + inputs=[x], + outputs=[y], + call_option={"length": x.size} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/tanh.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/tanh.py new file mode 100644 index 000000000..eb5b3eb4a --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/tanh.py @@ -0,0 +1,40 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.fallback.kernel import Kernel +from webdnn.graph.operators.tanh import Tanh + +# EcmaScript3 to support older browsers +# Math.tanh is EC6 feature + +source = """ +tanh: function(input_arrays, output_arrays, option) { +var x = input_arrays[0]; +var y = output_arrays[0]; +var length = option.length | 0; + +for (var i = 0; i < length; i++) { + var val = x[i]; + var exp2x = Math.exp(2 * val); + y[i] = (exp2x - 1.0) / (exp2x + 1.0); +} + +}, + +""" + + +# noinspection PyUnresolvedReferences +def tanh(op: Tanh, memory_layout: MemoryLayout) -> List[Kernel]: + x = op.inputs["x"] + y = op.outputs["y"] + + kernel = Kernel( + {"tanh": source}, + "tanh", + inputs=[x], + outputs=[y], + call_option={"length": x.size} + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/fallback/kernels/util.py b/src/graph_transpiler/webdnn/backend/fallback/kernels/util.py index d82789033..b11378616 100644 --- a/src/graph_transpiler/webdnn/backend/fallback/kernels/util.py +++ b/src/graph_transpiler/webdnn/backend/fallback/kernels/util.py @@ -1,15 +1,17 @@ -import numpy as np +from typing import Union from webdnn.graph.axis import Axis +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable +from webdnn.util.misc import mul -def calculate_stride(var: Variable, axis: Axis): +def calculate_stride(var: Variable, axis: Axis) -> Union[int, Placeholder]: """ - 行列の各次元のstride計算 - :param var: - :param axis: + calculate stride for specified dimension of specified variable. + + :param var: variable + :param axis: axis :return: """ - # noinspection PyTypeChecker - return int(np.prod(var.shape[var.order.axes_dict[axis] + 1:])) + return mul(var.shape[var.order.axes_dict[axis] + 1:]) diff --git a/src/graph_transpiler/webdnn/backend/interface/generator.py b/src/graph_transpiler/webdnn/backend/interface/generator.py index bcae17057..56854a5e8 100644 --- a/src/graph_transpiler/webdnn/backend/interface/generator.py +++ b/src/graph_transpiler/webdnn/backend/interface/generator.py @@ -1,32 +1,84 @@ import copy -from typing import Optional +from collections import defaultdict +from typing import Generic, TypeVar, Type, Callable, List, Dict -from webdnn.backend.fallback.generator import generate as generate_fallback +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.interface.graph_descriptor import IGraphExecutionData -from webdnn.backend.webassembly.generator import generate as generate_webassembly -from webdnn.backend.webgpu.generator import generate as generate_webgpu -from webdnn.frontend.general_optimize_rule import GeneralOptimizeRule +from webdnn.graph import traverse from webdnn.graph.graph import Graph -from webdnn.util import flags +from webdnn.graph.operator import Operator +from webdnn.optimizer.general_optimize_rule import GeneralOptimizeRule +from webdnn.util import console -# FIXME: ここでよい? +backend_names = ["webgpu", "webassembly", "fallback"] -generators = {"webgpu": generate_webgpu, - "webassembly": generate_webassembly, - "fallback": generate_fallback} +T_KERNEL = TypeVar("T_KERNEL") +T_EXEC_DATA = TypeVar("T_EXEC_DATA") -def generate_descriptor(backend: str, graph: Graph, constant_encoder_name: Optional[str] = None) -> IGraphExecutionData: - if backend not in generators: - raise NotImplementedError() +class DescriptorGenerator(Generic[T_KERNEL, T_EXEC_DATA]): + _handler_map = defaultdict(dict) # type: Dict[str, Dict[str, Callable[[Operator, MemoryLayout], List[T_KERNEL]]]] + + @classmethod + def generate(cls, graph: Graph, constant_encoder_name: str = None) -> T_EXEC_DATA: + raise NotImplementedError + + @classmethod + def register_handler(cls, OperatorClass: Type[Operator]): + key = OperatorClass.__name__ + + def decorator(handler: Callable[[Operator, MemoryLayout], List[T_KERNEL]]): + if key in cls._handler_map[cls.__name__]: + console.warning(f"[{cls.__name__}] Generator handler of '{key}' is already registered and overwritten.") + + cls._handler_map[cls.__name__][key] = handler + + return decorator + + @classmethod + def serialize_operator_type(cls, operator: Operator): + return operator.__class__.__name__ + + @classmethod + def generate_kernels(cls, graph: Graph, memory_layout: MemoryLayout) -> List[T_KERNEL]: + kernels = [] # Type: List[T_KERNEL] + + for op in traverse.listup_operators(graph): + key = cls.serialize_operator_type(op) + if key not in cls._handler_map[cls.__name__]: + raise NotImplementedError(f"Operator {op} is not handled by any generator handler") + + kernels += cls._handler_map[cls.__name__][key](op, memory_layout) + + return kernels + + +def get_generator(backend: str): + if backend == "webgpu": + from webdnn.backend.webgpu.generator import generate as generate_webgpu + return generate_webgpu + elif backend == "webassembly": + from webdnn.backend.webassembly.generator import generate as generate_webassembly + return generate_webassembly + elif backend == "fallback": + from webdnn.backend.fallback.generator import generate as generate_fallback + return generate_fallback + else: + raise NotImplementedError("No such backend") + + +def generate_descriptor(backend: str, graph: Graph, **kwargs) -> IGraphExecutionData: + generator = get_generator(backend) try: - graph = copy.deepcopy(graph) # FIXME: バックエンドごとの最適化でgraphが変わってしまうので入れてあるが、もっと良い方法があれば変更 + # Graph is transformed by backend-specific optimization + graph = copy.deepcopy(graph) except RecursionError: + # Occurs when the graph has many nodes (e.g. ResNet) raise RecursionError("Recursion error occurred when copying graph." + " sys.setrecursionlimit(10000) may help fixing it.") - if flags.optimize.OPTIMIZE: - graph, _ = GeneralOptimizeRule().optimize(graph) + # some optimize rule work even when OPTIMIZE=0 + graph, _ = GeneralOptimizeRule().optimize(graph) - return generators[backend](graph, constant_encoder_name=constant_encoder_name) + return generator(graph, **kwargs) diff --git a/src/graph_transpiler/webdnn/backend/interface/graph_descriptor.py b/src/graph_transpiler/webdnn/backend/interface/graph_descriptor.py index d8cf8d77d..747b0d11b 100644 --- a/src/graph_transpiler/webdnn/backend/interface/graph_descriptor.py +++ b/src/graph_transpiler/webdnn/backend/interface/graph_descriptor.py @@ -1,9 +1,20 @@ -class IGraphDescriptor: - pass +from typing import Generic, TypeVar, Iterable, Dict, Tuple, List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.graph.placeholder import Placeholder -class IGraphExecutionData: - descriptor: IGraphDescriptor +T_KERNEL = TypeVar("T_KERNEL") + + +class IGraphDescriptor(Generic[T_KERNEL]): + kernels: Iterable[T_KERNEL] + unresolved_value_list: List[Tuple[int, Placeholder]] + memory_layout: MemoryLayout + licenses: Dict[str, str] + + +class IGraphExecutionData(Generic[T_KERNEL]): + descriptor: IGraphDescriptor[T_KERNEL] constants: bytes backend_suffix: str diff --git a/src/graph_transpiler/webdnn/backend/interface/memory_layout.py b/src/graph_transpiler/webdnn/backend/interface/memory_layout.py deleted file mode 100644 index a47ec3be3..000000000 --- a/src/graph_transpiler/webdnn/backend/interface/memory_layout.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Dict - -from webdnn.graph.variable import Variable - - -class IAllocation: - variable: Variable - offset: int - - @property - def size(self) -> int: - raise NotImplementedError() - - -class IMemoryLayout: - size: int - __dict__: Dict[str, IAllocation] - - @property - def size(self) -> int: - raise NotImplementedError() diff --git a/src/graph_transpiler/webdnn/backend/webassembly/generator.py b/src/graph_transpiler/webdnn/backend/webassembly/generator.py index 7227ce7bd..77e2bee62 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/generator.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/generator.py @@ -7,52 +7,74 @@ import os import os.path as path +import platform import subprocess import sys -from typing import List +from webdnn.backend.code_generator.allocator import Allocator +from webdnn.backend.interface.generator import DescriptorGenerator from webdnn.backend.interface.graph_descriptor import IGraphExecutionData from webdnn.backend.webassembly.graph_descriptor import GraphDescriptor from webdnn.backend.webassembly.kernel import Kernel from webdnn.backend.webassembly.kernels.average_pooling_2d import average_pooling_2d from webdnn.backend.webassembly.kernels.axiswise_bias import axiswise_bias from webdnn.backend.webassembly.kernels.axiswise_scale import axiswise_scale +from webdnn.backend.webassembly.kernels.clipped_relu import clipped_relu from webdnn.backend.webassembly.kernels.col2im import col2im from webdnn.backend.webassembly.kernels.concat import concat from webdnn.backend.webassembly.kernels.elementwise_sum import elementwise_sum from webdnn.backend.webassembly.kernels.elu import elu +from webdnn.backend.webassembly.kernels.embedding import embedding from webdnn.backend.webassembly.kernels.flatten import flatten +from webdnn.backend.webassembly.kernels.hard_sigmoid import hard_sigmoid from webdnn.backend.webassembly.kernels.im2col import im2col -from webdnn.backend.webassembly.kernels.linear import linear +from webdnn.backend.webassembly.kernels.leaky_relu import leaky_relu from webdnn.backend.webassembly.kernels.local_response_normalization import local_response_normalization +from webdnn.backend.webassembly.kernels.lstm import lstm from webdnn.backend.webassembly.kernels.max_pooling_2d import max_pooling_2d +from webdnn.backend.webassembly.kernels.reinterpret_axis import reinterpret_axis from webdnn.backend.webassembly.kernels.relu import relu +from webdnn.backend.webassembly.kernels.reshape import reshape from webdnn.backend.webassembly.kernels.scalar_affine import scalar_affine from webdnn.backend.webassembly.kernels.sgemm import sgemm +from webdnn.backend.webassembly.kernels.sigmoid import sigmoid +from webdnn.backend.webassembly.kernels.softmax import softmax +from webdnn.backend.webassembly.kernels.softplus import softplus +from webdnn.backend.webassembly.kernels.softsign import softsign from webdnn.backend.webassembly.kernels.tanh import tanh +from webdnn.backend.webassembly.kernels.zero_padding_1d import zero_padding_1d from webdnn.backend.webassembly.operators.col2im import Col2Im from webdnn.backend.webassembly.operators.im2col import Im2Col from webdnn.backend.webassembly.operators.sgemm import Sgemm from webdnn.backend.webassembly.optimize_rules.webassembly_optimize_rule import WebassemblyOptimizeRule -from webdnn.backend.webgpu.allocator import Allocator, MemoryLayout from webdnn.encoder.constant_encoder import ConstantEncoder from webdnn.graph import traverse from webdnn.graph.graph import Graph -from webdnn.graph.operator import Operator from webdnn.graph.operators.average_pooling_2d import AveragePooling2D from webdnn.graph.operators.axiswise_bias import AxiswiseBias from webdnn.graph.operators.axiswise_scale import AxiswiseScale +from webdnn.graph.operators.clipped_relu import ClippedRelu from webdnn.graph.operators.concat import Concat from webdnn.graph.operators.elementwise_sum import ElementwiseSum from webdnn.graph.operators.elu import Elu +from webdnn.graph.operators.embedding import Embedding from webdnn.graph.operators.flatten import Flatten -from webdnn.graph.operators.linear import Linear +from webdnn.graph.operators.hard_sigmoid import HardSigmoid +from webdnn.graph.operators.leaky_relu import LeakyRelu from webdnn.graph.operators.local_response_normalization import LocalResponseNormalization +from webdnn.graph.operators.lstm import LSTM from webdnn.graph.operators.max_pooling_2d import MaxPooling2D +from webdnn.graph.operators.reinterpret_axis import ReinterpretAxis from webdnn.graph.operators.relu import Relu +from webdnn.graph.operators.reshape import Reshape from webdnn.graph.operators.scalar_affine import ScalarAffine +from webdnn.graph.operators.sigmoid import Sigmoid +from webdnn.graph.operators.softmax import Softmax +from webdnn.graph.operators.softplus import Softplus +from webdnn.graph.operators.softsign import Softsign from webdnn.graph.operators.tanh import Tanh -from webdnn.util import flags +from webdnn.graph.operators.zero_padding_1d import ZeroPadding1D +from webdnn.util import flags, console from webdnn.util.json import json @@ -63,6 +85,7 @@ def __init__(self, descriptor: GraphDescriptor, constants: bytes): self.descriptor = descriptor self.constants = constants self.backend_suffix = "webassembly" + self.platform_windows = platform.system() == "Windows" # workaround for PATH problem def save(self, dirname: str): os.makedirs(dirname, exist_ok=True) @@ -86,31 +109,33 @@ def _compile(self, dirname: str): args.append("-O3") args.append("-std=c++11") args.append("-s") - args.append("EXPORTED_FUNCTIONS=['_run','_init','_get_weight_buffer','_get_data_buffer']") + args.append("EXPORTED_FUNCTIONS=['_run','_init','_get_static_buffer','_allocate_dynamic_buffer','_get_dynamic_buffer','_set_placeholder_value']") args.append("-s") args.append("WASM=1") args.append("-s") args.append(f"TOTAL_MEMORY={self.descriptor.required_heap}") + args.append("-s") + args.append(f"ALLOW_MEMORY_GROWTH=1") # cannot be used in asm.js args.append("--pre-js") args.append(path.join(path.dirname(__file__), "webassembly_header.js")) args.append("-o") args.append(path.join(dirname, "kernels_{}.js".format(self.backend_suffix))) try: - subprocess.check_call(args) + subprocess.check_call(args, shell=self.platform_windows) except Exception as ex: sys.stderr.write("Executing em++ command failed." + " Make sure emscripten is properly installed and environment variables are set.\n") raise ex def _compile_fallback_asmjs(self, dirname: str): - # noinspection PyListCreation backend_suffix = "asmjs" + # noinspection PyListCreation args = ["em++"] args.append(path.join(dirname, "kernels_{}.cpp".format(self.backend_suffix))) args.append("-O3") args.append("-std=c++11") args.append("-s") - args.append("EXPORTED_FUNCTIONS=['_run','_init','_get_weight_buffer','_get_data_buffer']") + args.append("EXPORTED_FUNCTIONS=['_run','_init','_get_static_buffer','_allocate_dynamic_buffer','_get_dynamic_buffer','_set_placeholder_value']") args.append("-s") args.append(f"TOTAL_MEMORY={self.descriptor.required_heap}") args.append("--pre-js") @@ -118,107 +143,83 @@ def _compile_fallback_asmjs(self, dirname: str): args.append("-o") args.append(path.join(dirname, "kernels_{}.js".format(backend_suffix))) try: - subprocess.check_call(args) + subprocess.check_call(args, shell=self.platform_windows) except Exception as ex: sys.stderr.write("Executing em++ command failed." + " Make sure emscripten is properly installed and environment variables are set.\n") raise ex -def generate(graph: Graph, constant_encoder_name: str = None) -> GraphExecutionData: - graph, _ = WebassemblyOptimizeRule().optimize(graph) - if flags.DEBUG: - traverse.dump(graph) - - variables_layout, constants_layout, constants_data = Allocator.allocate(graph) - if flags.DEBUG: - print( - f"[GraphDescriptorGeneratorWebassembly] constants_layout total size: {constants_data.size} * sizeof(float)") - print( - f"[GraphDescriptorGeneratorWebassembly] variables_layout total size: {variables_layout.size} * sizeof(float)") - constant_encoder = ConstantEncoder.get_encoder(constant_encoder_name) - constants_bytes = constant_encoder.encode(constants_layout, constants_data) - if flags.DEBUG: - print(f"[GraphDescriptorGeneratorWebGPU] constants encoded size: {len(constants_bytes)}") - - kernels = generate_kernels(graph, constants_layout, variables_layout) - - weight_data_size = (variables_layout.size + constants_layout.size) * 4 # sizeof(float) - required_heap = (int(weight_data_size // (16 * 1048576)) + 2) * 16 * 1048576 # required + 16MB - - descriptor = GraphDescriptor( - kernels=kernels, - constants_layout=constants_layout, - variables_layout=variables_layout, - inputs=graph.inputs, - outputs=graph.outputs, - constants_encoding=constant_encoder.name, - required_heap=required_heap, - licenses=graph.licenses) - - return GraphExecutionData(descriptor, constants_bytes) - - -def generate_kernels(graph: Graph, constants_layout: MemoryLayout, variables_layout: MemoryLayout) -> List[Kernel]: - kernels: List[Kernel] = [] - - for op in traverse.listup_operators(graph): - if isinstance(op, Linear): - kernels += linear(op, constants_layout, variables_layout) - - elif isinstance(op, AxiswiseBias): - kernels += axiswise_bias(op, constants_layout, variables_layout) +class WebassemblyDescriptorGenerator(DescriptorGenerator[Kernel, GraphExecutionData]): + @classmethod + def generate(cls, graph: Graph, **kwargs): + graph, _ = WebassemblyOptimizeRule().optimize(graph) + if flags.DEBUG: + traverse.dump(graph) - elif isinstance(op, Relu): - kernels += relu(op, constants_layout, variables_layout) + memory_layout = Allocator.allocate(graph) - elif isinstance(op, Elu): - kernels += elu(op, constants_layout, variables_layout) + console.debug(f"[WebassemblyDescriptorGenerator] memory_layout total size: {memory_layout.total_size * 4}") + console.debug(f"[WebassemblyDescriptorGenerator] memory_layout static size: {memory_layout.static_size * 4}") + console.debug(f"[WebassemblyDescriptorGenerator] memory_layout dynamic size: {memory_layout.dynamic_size * 4}") - elif isinstance(op, Tanh): - kernels += tanh(op, constants_layout, variables_layout) + constant_encoder = ConstantEncoder.get_encoder(kwargs.get("constant_encoder_name", None)) + constants_bytes = constant_encoder.encode(memory_layout) - elif isinstance(op, LocalResponseNormalization): - kernels += local_response_normalization(op, constants_layout, variables_layout) + console.debug(f"[WebassemblyDescriptorGenerator] constants encoded size: {len(constants_bytes)}") - elif isinstance(op, MaxPooling2D): - kernels += max_pooling_2d(op, constants_layout, variables_layout) - - elif isinstance(op, AveragePooling2D): - kernels += average_pooling_2d(op, constants_layout, variables_layout) - - elif isinstance(op, AxiswiseScale): - kernels += axiswise_scale(op, constants_layout, variables_layout) - - elif isinstance(op, ElementwiseSum): - kernels += elementwise_sum(op, constants_layout, variables_layout) - - elif isinstance(op, Flatten): - kernels += flatten(op, constants_layout, variables_layout) - - elif isinstance(op, Sgemm): - kernels += sgemm(op, constants_layout, variables_layout) - - elif isinstance(op, Im2Col): - kernels += im2col(op, constants_layout, variables_layout) - - elif isinstance(op, Col2Im): - kernels += col2im(op, constants_layout, variables_layout) - - elif isinstance(op, ScalarAffine): - kernels += scalar_affine(op, constants_layout, variables_layout) - - elif isinstance(op, Concat): - kernels += concat(op, constants_layout, variables_layout) - - elif isinstance(op, Operator): - if "custom_kernel" in op.parameters: - kernels += op.parameters["custom_kernel"](op, constants_layout, variables_layout) - continue - - raise NotImplementedError(f"{op} is Unknown for WebassemblyDescriptorGenerator") + kernels = cls.generate_kernels(graph, memory_layout) + heap_block_size = 16 * 1024 * 1024 + if isinstance(memory_layout.dynamic_size, int): + dynamic_size_byte_int = memory_layout.dynamic_size * 4 else: - raise NotImplementedError(f"{op} is Unknown for WebassemblyDescriptorGenerator") - - return kernels + dynamic_size_byte_int = kwargs.get("dynamic_allocation_size", heap_block_size) + total_size_byte = memory_layout.static_size * 4 + dynamic_size_byte_int + + # required for calculation (size ceiling to one block) + one block + required_heap = ((total_size_byte + heap_block_size - 1) // heap_block_size + 1) * heap_block_size + + descriptor = GraphDescriptor( + kernels=kernels, + memory_layout=memory_layout, + inputs=graph.inputs, + outputs=graph.outputs, + constants_encoding=constant_encoder.name, + required_heap=required_heap, + licenses=graph.licenses) + + return GraphExecutionData(descriptor, constants_bytes) + + +def generate(graph: Graph, **kwargs): + return WebassemblyDescriptorGenerator.generate(graph, **kwargs) + + +WebassemblyDescriptorGenerator.register_handler(AveragePooling2D)(average_pooling_2d) +WebassemblyDescriptorGenerator.register_handler(AxiswiseBias)(axiswise_bias) +WebassemblyDescriptorGenerator.register_handler(AxiswiseScale)(axiswise_scale) +WebassemblyDescriptorGenerator.register_handler(ClippedRelu)(clipped_relu) +WebassemblyDescriptorGenerator.register_handler(Col2Im)(col2im) +WebassemblyDescriptorGenerator.register_handler(Concat)(concat) +WebassemblyDescriptorGenerator.register_handler(ElementwiseSum)(elementwise_sum) +WebassemblyDescriptorGenerator.register_handler(Elu)(elu) +WebassemblyDescriptorGenerator.register_handler(Embedding)(embedding) +WebassemblyDescriptorGenerator.register_handler(Flatten)(flatten) +WebassemblyDescriptorGenerator.register_handler(HardSigmoid)(hard_sigmoid) +WebassemblyDescriptorGenerator.register_handler(Im2Col)(im2col) +WebassemblyDescriptorGenerator.register_handler(LeakyRelu)(leaky_relu) +WebassemblyDescriptorGenerator.register_handler(LocalResponseNormalization)(local_response_normalization) +WebassemblyDescriptorGenerator.register_handler(LSTM)(lstm) +WebassemblyDescriptorGenerator.register_handler(MaxPooling2D)(max_pooling_2d) +WebassemblyDescriptorGenerator.register_handler(ScalarAffine)(scalar_affine) +WebassemblyDescriptorGenerator.register_handler(Sgemm)(sgemm) +WebassemblyDescriptorGenerator.register_handler(Sigmoid)(sigmoid) +WebassemblyDescriptorGenerator.register_handler(Softmax)(softmax) +WebassemblyDescriptorGenerator.register_handler(Softplus)(softplus) +WebassemblyDescriptorGenerator.register_handler(Softsign)(softsign) +WebassemblyDescriptorGenerator.register_handler(ReinterpretAxis)(reinterpret_axis) +WebassemblyDescriptorGenerator.register_handler(Relu)(relu) +WebassemblyDescriptorGenerator.register_handler(Reshape)(reshape) +WebassemblyDescriptorGenerator.register_handler(Tanh)(tanh) +WebassemblyDescriptorGenerator.register_handler(ZeroPadding1D)(zero_padding_1d) diff --git a/src/graph_transpiler/webdnn/backend/webassembly/graph_descriptor.py b/src/graph_transpiler/webdnn/backend/webassembly/graph_descriptor.py index 0565ef2fb..ab7db769e 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/graph_descriptor.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/graph_descriptor.py @@ -1,12 +1,13 @@ from collections import OrderedDict -from typing import Dict, Iterable +from typing import Dict, Iterable, List, Set, Tuple import numpy as np +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.interface.graph_descriptor import IGraphDescriptor from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph import traverse +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable from webdnn.graph.variables.attributes.constant import Constant from webdnn.util import json @@ -15,25 +16,35 @@ #include #include -float weight_buffer[%%WEIGHT_SIZE%%]; -float data_buffer[%%DATA_SIZE%%]; +float static_buffer[%%STATIC_SIZE%%]; +float* dynamic_buffer = nullptr; """ source_init = """ extern "C" void init() { - //weight_buffer = (float*)malloc(%%WEIGHT_SIZE%% * sizeof(float)); - //data_buffer = (float*)malloc(%%DATA_SIZE%% * sizeof(float)); + //static_buffer = (float*)malloc(%%STATIC_SIZE%% * sizeof(float)); } -extern "C" float* get_weight_buffer(void) { - return weight_buffer; +extern "C" float* get_static_buffer(void) { + return static_buffer; } -extern "C" float* get_data_buffer(void) { - return data_buffer; +extern "C" float* allocate_dynamic_buffer(int count) { + if (dynamic_buffer) { + free(dynamic_buffer); + dynamic_buffer = nullptr; + } + dynamic_buffer = (float*)malloc(count * sizeof(float)); + return dynamic_buffer; } +extern "C" float* get_dynamic_buffer(void) { + return dynamic_buffer; +} +extern "C" void set_placeholder_value(int kernel_order, int offset, int value) { + meta_buffers[kernel_order][offset] = value; +} """ source_exec = """ @@ -45,63 +56,68 @@ class GraphDescriptor(json.SerializableMixin, IGraphDescriptor): - kernels: Iterable[Kernel] - constants_layout: MemoryLayout - variables_layout: MemoryLayout + kernels: List[Kernel] + memory_layout: MemoryLayout inputs: Iterable[Variable] outputs: Iterable[Variable] constants_encoding: str + header_sources: Dict[str, str] footer_sources: Dict[str, str] required_heap: int licenses: Dict[str, str] def __init__(self, - kernels: Iterable[Kernel], - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, + kernels: List[Kernel], + memory_layout: MemoryLayout, inputs: Iterable[Variable], outputs: Iterable[Variable], constants_encoding: str, required_heap: int, licenses: Dict[str, str]): self.kernels = kernels - self.constants_layout = constants_layout - self.variables_layout = variables_layout + self.memory_layout = memory_layout self.inputs = inputs self.outputs = outputs self.constants_encoding = constants_encoding + self.header_sources = OrderedDict() self.footer_sources = OrderedDict() self.required_heap = required_heap self.licenses = licenses - def generate_header_source(self): - return source_header \ - .replace("%%WEIGHT_SIZE%%", str(self.constants_layout.size)) \ - .replace("%%DATA_SIZE%%", str(self.variables_layout.size)) + def generate_top_source(self): + self.header_sources["top"] = source_header \ + .replace("%%STATIC_SIZE%%", str(self.memory_layout.static_size)) def generate_init_source(self): - self.footer_sources["init"] = source_init \ - .replace("%%WEIGHT_SIZE%%", str(self.constants_layout.size)) \ - .replace("%%DATA_SIZE%%", str(self.variables_layout.size)) + self.header_sources["init"] = source_init \ + .replace("%%STATIC_SIZE%%", str(self.memory_layout.static_size)) # noinspection PyMethodMayBeStatic def generate_exec_line(self, kernel: Kernel, serial: int): - line = f"const int meta_buf_{serial}[] = {{" + line = f"{kernel.exec_info.entry_func_name}(meta_buf_{serial});\n" + return line + + def generate_kernel_metabuffer_initializer_line(self, kernel: Kernel, serial: int): + line = "int meta_buf_" + str(serial) + "[] = {" # see as int32 and convert to 12345,67890 line += ",".join(map(str, np.fromstring(kernel.exec_info.meta_buffer, dtype=np.int32).tolist())) line += "};\n" - line += f"{kernel.exec_info.entry_func_name}(meta_buf_{serial});\n" return line def generate_exec_source(self): lines = "" + meta_buffer_initializer = "" for serial, kernel in enumerate(self.kernels): + meta_buffer_initializer += self.generate_kernel_metabuffer_initializer_line(kernel, serial) lines += self.generate_exec_line(kernel, serial) + meta_buffer_initializer += "int* meta_buffers[] = {" + meta_buffer_initializer += ",".join(["meta_buf_" + str(serial) for serial in range(len(self.kernels))]) + meta_buffer_initializer += "};\n" self.footer_sources["exec"] = source_exec.replace("%%EXEC_LINES%%", lines) + self.header_sources["meta_buffer_initializer"] = meta_buffer_initializer def concat_kernel_sources(self): func_sources = OrderedDict() - prototype_sources = OrderedDict() for kernel in self.kernels: for func_name, source in kernel.func_sources.items(): @@ -110,27 +126,42 @@ def concat_kernel_sources(self): else: func_sources[func_name] = source - for func_name, source in kernel.prototype_sources.items(): - if func_name in prototype_sources: - assert prototype_sources[func_name] == source - else: - prototype_sources[func_name] = source - - self.generate_init_source() + self.generate_top_source() self.generate_exec_source() + self.generate_init_source() combined_source = \ - self.generate_header_source() + \ - "\n".join(prototype_sources.values()) + \ + "".join(self.header_sources.values()) + \ "\n".join(func_sources.values()) + \ - "\n".join(self.footer_sources.values()) + "".join(self.footer_sources.values()) return combined_source + def get_all_placeholders(self): + unresolved_variables = [] # type: List[Tuple[int, Placeholder]] + placeholders_set = set() # type: Set[Placeholder] + + for kernel in self.kernels: + unresolved_variables += kernel.exec_info.unresolved_value_list + + for offset, v in unresolved_variables: + placeholders_set.update(v.get_depend_placeholders()) + + placeholders = {p.label: None for p in placeholders_set} + + return placeholders + def _to_serializable_(self): + placeholders = self.get_all_placeholders() + unresolved_value_lists = [] + for kernel in self.kernels: + unresolved_value_list = kernel.exec_info.unresolved_value_list or [] + unresolved_value_lists.append([{"offset": v[0], "placeholder": v[1]} for v in unresolved_value_list]) + return { - "weight_allocation": self.constants_layout, "weight_encoding": self.constants_encoding, - "variable_allocation": self.variables_layout, + "memory_layout": self.memory_layout, + "placeholders": placeholders, + "unresolved_value_lists": unresolved_value_lists, "inputs": [v.parameters["name"] for v in self.inputs if not traverse.check_attribute_match(v, Constant)], "outputs": [v.parameters["name"] for v in self.outputs], "licenses": self.licenses diff --git a/src/graph_transpiler/webdnn/backend/webassembly/inline_injector.py b/src/graph_transpiler/webdnn/backend/webassembly/inline_injector.py deleted file mode 100644 index 8772f60b4..000000000 --- a/src/graph_transpiler/webdnn/backend/webassembly/inline_injector.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Callable - -from webdnn.backend.webassembly.tag_parser import TagParser - - -class InlineInjector: - delegate: Callable[[str], str] - - def __init__(self): - self.delegate = lambda exp: exp - - def inject(self, source: str) -> str: - for tag in TagParser.parse(source): - if tag.name == "INLINE": - source = source[:tag.span[0]] + self.delegate(tag.args[0]) + source[tag.span[1]:] - - return source diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernel.py b/src/graph_transpiler/webdnn/backend/webassembly/kernel.py index a39ffc8b4..39eaa6431 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernel.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernel.py @@ -1,7 +1,8 @@ -from typing import Dict +from typing import Dict, List, Tuple import numpy as np +from webdnn.graph.placeholder import Placeholder from webdnn.util import json @@ -11,30 +12,32 @@ class KernelExecutionInfo(json.SerializableMixin): def __init__(self, entry_func_name: str, - meta_buffer: bytes): + meta_buffer: bytes, + unresolved_value_list: List[Tuple[int, Placeholder]] = None): self.entry_func_name = entry_func_name self.meta_buffer = meta_buffer + self.unresolved_value_list = [] if unresolved_value_list is None else unresolved_value_list # type:List[Tuple[int, Placeholder]] def _to_serializable_(self): return { "entry_func_name": self.entry_func_name, - "meta_buffer": np.frombuffer(self.meta_buffer, dtype=np.uint8).tolist() + "meta_buffer": np.frombuffer(self.meta_buffer, dtype=np.uint8).tolist(), + "unresolved_value_list": [{"offset": v[0], "placeholder": v[1]} for v in self.unresolved_value_list] } class Kernel: func_sources: Dict[str, str] - prototype_sources: Dict[str, str] exec_info: KernelExecutionInfo def __init__(self, func_sources: Dict[str, str], entry_func_name: str, meta_buffer: bytes, - prototype_sources: Dict[str, str] = None): + unresolved_value_list: List[Tuple[int, Placeholder]] = None): self.func_sources = func_sources - self.prototype_sources = {} if prototype_sources is None else prototype_sources self.exec_info = KernelExecutionInfo( entry_func_name=entry_func_name, - meta_buffer=meta_buffer + meta_buffer=meta_buffer, + unresolved_value_list=[] if unresolved_value_list is None else unresolved_value_list ) diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/average_pooling_2d.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/average_pooling_2d.py index 5de523cf3..95487996d 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/average_pooling_2d.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/average_pooling_2d.py @@ -1,30 +1,32 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.axis import Axis from webdnn.graph.operators.average_pooling_2d import AveragePooling2D from webdnn.graph.order import OrderNHWC template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(average_pooling_2d_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(average_pooling_2d_Y_offset)%%; - const int N = %%META_LOAD(average_pooling_2d_N)%%; - const int H1 = %%META_LOAD(average_pooling_2d_H1)%%; - const int W1 = %%META_LOAD(average_pooling_2d_W1)%%; - const int C = %%META_LOAD(average_pooling_2d_C)%%; - const int H2 = %%META_LOAD(average_pooling_2d_H2)%%; - const int W2 = %%META_LOAD(average_pooling_2d_W2)%%; - const int K = %%META_LOAD(average_pooling_2d_K)%%; - const int S = %%META_LOAD(average_pooling_2d_S)%%; - const int P = %%META_LOAD(average_pooling_2d_P)%%; + const float *X = %%LOAD_BUFFER(average_pooling_2d_X)%%; + float *Y = %%LOAD_BUFFER(average_pooling_2d_Y)%%; + const int N = %%LOAD_BUFFER(average_pooling_2d_N)%%; + const int H1 = %%LOAD_BUFFER(average_pooling_2d_H1)%%; + const int W1 = %%LOAD_BUFFER(average_pooling_2d_W1)%%; + const int C = %%LOAD_BUFFER(average_pooling_2d_C)%%; + const int H2 = %%LOAD_BUFFER(average_pooling_2d_H2)%%; + const int W2 = %%LOAD_BUFFER(average_pooling_2d_W2)%%; + + const int KH = %%LOAD_BUFFER(average_pooling_2d_KH)%%; + const int KW = %%LOAD_BUFFER(average_pooling_2d_KW)%%; + const int SH = %%LOAD_BUFFER(average_pooling_2d_SH)%%; + const int SW = %%LOAD_BUFFER(average_pooling_2d_SW)%%; + const int PH = %%LOAD_BUFFER(average_pooling_2d_PH)%%; + const int PW = %%LOAD_BUFFER(average_pooling_2d_PW)%%; - //%%INITIALIZER_ATTACHABLE_PLACEHOLDER%% - for (int gid = 0; gid < N * H2 * W2 * C; gid += 1) { const int c = gid % C; const int w2 = gid / C % W2; @@ -32,20 +34,19 @@ const int n = gid / C / W2 / H2; float v = 0; - for (int kh = 0; kh < K; kh++) { - const int h1 = h2 * S - P + kh; + for (int kh = 0; kh < KH; kh++) { + const int h1 = h2 * SH - PH + kh; if (h1 < 0 || h1 >= H1) continue; - for (int kw = 0; kw < K; kw++) { - const int w1 = w2 * S - P + kw; + for (int kw = 0; kw < KW; kw++) { + const int w1 = w2 * SW - PW + kw; if (w1 < 0 || w1 >= W1) continue; v += X[((n * H1 + h1) * W1 + w1) * C + c]; } } - v /= K * K; + v /= KH * KW; - //Y[gid] = %%CHANNELWISE_ATTACHABLE(v, n)%%; Y[gid] = v; } } @@ -53,41 +54,42 @@ # noinspection PyUnusedLocal -def average_pooling_2d(op: AveragePooling2D, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] +def average_pooling_2d(op: AveragePooling2D, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.order == OrderNHWC assert y.variable.order == OrderNHWC - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - metabuffer_injector.register({ - "average_pooling_2d_X_offset": x.offset, - "average_pooling_2d_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "average_pooling_2d_X": x, + "average_pooling_2d_Y": y, "average_pooling_2d_N": x.variable.shape_dict[Axis.N], "average_pooling_2d_H1": x.variable.shape_dict[Axis.H], "average_pooling_2d_W1": x.variable.shape_dict[Axis.W], "average_pooling_2d_C": x.variable.shape_dict[Axis.C], "average_pooling_2d_H2": y.variable.shape_dict[Axis.H], "average_pooling_2d_W2": y.variable.shape_dict[Axis.W], - "average_pooling_2d_K": op.parameters["ksize"][0], - "average_pooling_2d_S": op.parameters["stride"][0], - "average_pooling_2d_P": op.parameters["padding"][0], + "average_pooling_2d_KH": op.parameters["ksize"][0], + "average_pooling_2d_KW": op.parameters["ksize"][1], + "average_pooling_2d_SH": op.parameters["stride"][0], + "average_pooling_2d_SW": op.parameters["stride"][1], + "average_pooling_2d_PH": op.parameters["padding"][0], + "average_pooling_2d_PW": op.parameters["padding"][1], }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("average_pooling_2d", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/axiswise_bias.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/axiswise_bias.py index 041126edb..0e0007f16 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/axiswise_bias.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/axiswise_bias.py @@ -1,72 +1,154 @@ from typing import List -from webdnn.backend.webassembly.inline_injector import InlineInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.graph.axis import Axis from webdnn.graph.operators.axiswise_bias import AxiswiseBias -from webdnn.graph.order import OrderHWNC, OrderNHWC, OrderNC +from webdnn.util.misc import mul -template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) + +def axiswise_bias(op: AxiswiseBias, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + if x.variable.order == y.variable.order: + return axiswise_bias_same_order(op, memory_layout) + + else: + return axiswise_bias_general(op, memory_layout) + + +template_same_order = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(axiswise_bias_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(axiswise_bias_Y_offset)%%; - const float *B = weight_buffer + %%META_LOAD(axiswise_bias_B_offset)%%; - const int N = %%META_LOAD(axiswise_bias_N)%%; - const int C = %%META_LOAD(axiswise_bias_C)%%; - - for (int gid = 0; gid < N * C; gid += 1) { - int c = gid % C; - int n = gid / C; - - float result = X[gid] + B[c]; - Y[n * C + c] = %%INLINE(result)%%; + const float *X = %%LOAD_BUFFER(axiswise_bias_X)%%; + float *Y = %%LOAD_BUFFER(axiswise_bias_Y)%%; + const float *B = %%LOAD_BUFFER(axiswise_bias_B)%%; + const int D1 = %%LOAD_BUFFER(axiswise_bias_D1)%%; + const int D2 = %%LOAD_BUFFER(axiswise_bias_D2)%%; + const int D3 = %%LOAD_BUFFER(axiswise_bias_D3)%%; + + for (int index = 0; index < D1 * D2 * D3; index++) { + Y[index] = X[index] + B[index / D3 % D2]; } } """ -def axiswise_bias(op: AxiswiseBias, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - b = constants_layout[op.inputs["b"]] - y = variables_layout[op.outputs["y"]] - - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - assert x.variable.order == OrderNC or x.variable.order == OrderNHWC or x.variable.order == OrderHWNC - assert y.variable.shape == x.variable.shape - - assert op.parameters["axis"] == Axis.C, "[Webassembly] AxiswiseBias supports only channelwise bias." - - metabuffer_injector.register({ - "axiswise_bias_X_offset": x.offset, - "axiswise_bias_Y_offset": y.offset, - "axiswise_bias_B_offset": b.offset, - "axiswise_bias_N": y.variable.size // y.variable.shape_dict[Axis.C], - "axiswise_bias_C": y.variable.shape_dict[Axis.C], +def axiswise_bias_same_order(op: AxiswiseBias, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + b = memory_layout[op.inputs["b"]] + y = memory_layout[op.outputs["y"]] + + target_axis_index = x.variable.order.axes_dict[op.axis] + D1 = mul(x.variable.shape[:target_axis_index]) + D2 = x.variable.shape[target_axis_index] + D3 = mul(x.variable.shape[target_axis_index + 1:]) + + buffer_injector = BufferInjector() + buffer_injector.register({ + "axiswise_bias_X": x, + "axiswise_bias_B": b, + "axiswise_bias_Y": y, + "axiswise_bias_D1": D1, + "axiswise_bias_D2": D2, + "axiswise_bias_D3": D3 + }) + + name_injector = KernelNameInjector(op) + + source = template_same_order + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] + + +template_general = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(axiswise_bias_X)%%; + float *Y = %%LOAD_BUFFER(axiswise_bias_Y)%%; + const float *B = %%LOAD_BUFFER(axiswise_bias_B)%%; + + const int D1 = %%LOAD_BUFFER(axiswise_bias_D1)%%; + const int D2 = %%LOAD_BUFFER(axiswise_bias_D2)%%; + const int D3 = %%LOAD_BUFFER(axiswise_bias_D3)%%; + const int D = %%LOAD_BUFFER(axiswise_bias_D)%%; + const int d_target = %%LOAD_BUFFER(axiswise_bias_d_target)%%; + const int *x_shape = %%LOAD_BUFFER(axiswise_bias_x_shape)%%; + const int *x_stride_in_y = %%LOAD_BUFFER(axiswise_bias_x_stride_in_y)%%; + + for (int index = 0; index < D1 * D2 * D3; index++) { + int y_offset = 0; + int s = index; + for (int d = D - 1; d >= 0; d--) { + y_offset += x_stride_in_y[d] * (s % x_shape[d]); + s /= x_shape[d]; + } + + Y[y_offset] = X[index] + B[index / D3 % D2]; + } +} +""" + + +def axiswise_bias_general(op: AxiswiseBias, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + b = memory_layout[op.inputs["b"]] + y = memory_layout[op.outputs["y"]] + + x_shape = x.variable.shape + + target_axis_index = x.variable.order.axes_dict[op.axis] + D1 = mul(x_shape[:target_axis_index]) + D2 = x_shape[target_axis_index] + D3 = mul(x_shape[target_axis_index + 1:]) + + y_strides = [] + stride = 1 + for sh in reversed(y.variable.shape): + y_strides.insert(0, stride) + stride *= sh + + x_stride_in_y = [y_strides[y.variable.order.axes_dict[axis]] for axis in x.variable.order.axes] + + buffer_injector = BufferInjector() + buffer_injector.register({ + "axiswise_bias_X": x, + "axiswise_bias_B": b, + "axiswise_bias_Y": y, + "axiswise_bias_D1": D1, + "axiswise_bias_D2": D2, + "axiswise_bias_D3": D3, + "axiswise_bias_D": x.variable.ndim, + "axiswise_bias_d_target": x.variable.order.axes_dict[op.axis], + "axiswise_bias_x_shape": x_shape, + "axiswise_bias_x_stride_in_y": x_stride_in_y, }) - inline_injector = InlineInjector() - if "inline_elementwise" in op.parameters: - inline_injector.delegate = op.parameters["inline_elementwise"] + name_injector = KernelNameInjector(op) - source = template - source = metabuffer_injector.inject(source) - source = inline_injector.inject(source) - func_name = util.add_canonical_suffix("axiswise_bias", source) - source = source.replace("%%FUNC_NAME%%", func_name) + source = template_general + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/axiswise_scale.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/axiswise_scale.py index 1d7ebec09..e573de18a 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/axiswise_scale.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/axiswise_scale.py @@ -1,64 +1,154 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.graph.axis import Axis from webdnn.graph.operators.axiswise_scale import AxiswiseScale -from webdnn.graph.order import OrderNHWC, OrderNC, OrderHWNC +from webdnn.util.misc import mul -template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) + +def axiswise_scale(op: AxiswiseScale, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + if x.variable.order == y.variable.order: + return axiswise_scale_same_order(op, memory_layout) + + else: + return axiswise_scale_general(op, memory_layout) + + +template_same_order = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(axiswise_scale_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(axiswise_scale_Y_offset)%%; - const float *S = weight_buffer + %%META_LOAD(axiswise_scale_S_offset)%%; - const int N = %%META_LOAD(axiswise_scale_N)%%; - const int C = %%META_LOAD(axiswise_scale_C)%%; - - for (int gid = 0; gid < N; gid += 1) { - int c = gid % C; - - float result = X[gid] * S[c]; - //Y[gid] = %%CHANNELWISE_ATTACHABLE(result, c)%%; - Y[gid] = result; + const float *X = %%LOAD_BUFFER(axiswise_scale_X)%%; + float *Y = %%LOAD_BUFFER(axiswise_scale_Y)%%; + const float *S = %%LOAD_BUFFER(axiswise_scale_S)%%; + const int D1 = %%LOAD_BUFFER(axiswise_scale_D1)%%; + const int D2 = %%LOAD_BUFFER(axiswise_scale_D2)%%; + const int D3 = %%LOAD_BUFFER(axiswise_scale_D3)%%; + + for (int index = 0; index < D1 * D2 * D3; index++) { + Y[index] = X[index] * S[index / D3 % D2]; } } """ -def axiswise_scale(op: AxiswiseScale, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - s = constants_layout[op.inputs["s"]] - y = variables_layout[op.outputs["y"]] - - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - assert x.variable.order == OrderNC or x.variable.order == OrderNHWC or x.variable.order == OrderHWNC - assert y.variable.order == OrderNC or y.variable.order == OrderNHWC or y.variable.order == OrderHWNC - assert op.parameters["axis"] == Axis.C, "[Webassembly] AxiswiseScale supports only channelwise bias." - - metabuffer_injector.register({ - "axiswise_scale_X_offset": x.offset, - "axiswise_scale_Y_offset": y.offset, - "axiswise_scale_S_offset": s.offset, - "axiswise_scale_N": y.variable.size, - "axiswise_scale_C": y.variable.shape_dict[Axis.C], +def axiswise_scale_same_order(op: AxiswiseScale, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + s = memory_layout[op.inputs["s"]] + y = memory_layout[op.outputs["y"]] + + target_axis_index = x.variable.order.axes_dict[op.axis] + D1 = mul(x.variable.shape[:target_axis_index]) + D2 = x.variable.shape[target_axis_index] + D3 = mul(x.variable.shape[target_axis_index + 1:]) + + buffer_injector = BufferInjector() + buffer_injector.register({ + "axiswise_scale_X": x, + "axiswise_scale_S": s, + "axiswise_scale_Y": y, + "axiswise_scale_D1": D1, + "axiswise_scale_D2": D2, + "axiswise_scale_D3": D3 }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("axiswise_scale", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template_same_order + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] + + +template_general = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(axiswise_scale_X)%%; + float *Y = %%LOAD_BUFFER(axiswise_scale_Y)%%; + const float *S = %%LOAD_BUFFER(axiswise_scale_S)%%; + + const int D1 = %%LOAD_BUFFER(axiswise_scale_D1)%%; + const int D2 = %%LOAD_BUFFER(axiswise_scale_D2)%%; + const int D3 = %%LOAD_BUFFER(axiswise_scale_D3)%%; + const int D = %%LOAD_BUFFER(axiswise_scale_D)%%; + const int d_target = %%LOAD_BUFFER(axiswise_scale_d_target)%%; + const int *x_shape = %%LOAD_BUFFER(axiswise_scale_x_shape)%%; + const int *x_stride_in_y = %%LOAD_BUFFER(axiswise_scale_x_stride_in_y)%%; + + for (int index = 0; index < D1 * D2 * D3; index++) { + int y_offset = 0; + int s = index; + for (int d = D - 1; d >= 0; d--) { + y_offset += x_stride_in_y[d] * (s % x_shape[d]); + s /= x_shape[d]; + } + + Y[y_offset] = X[index] * S[index / D3 % D2]; + } +} +""" + + +def axiswise_scale_general(op: AxiswiseScale, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + s = memory_layout[op.inputs["s"]] + y = memory_layout[op.outputs["y"]] + + x_shape = x.variable.shape + + target_axis_index = x.variable.order.axes_dict[op.axis] + D1 = mul(x_shape[:target_axis_index]) + D2 = x_shape[target_axis_index] + D3 = mul(x_shape[target_axis_index + 1:]) + + y_strides = [] + stride = 1 + for sh in reversed(y.variable.shape): + y_strides.insert(0, stride) + stride *= sh + + x_stride_in_y = [y_strides[y.variable.order.axes_dict[axis]] for axis in x.variable.order.axes] + + buffer_injector = BufferInjector() + buffer_injector.register({ + "axiswise_scale_X": x, + "axiswise_scale_S": s, + "axiswise_scale_Y": y, + "axiswise_scale_D1": D1, + "axiswise_scale_D2": D2, + "axiswise_scale_D3": D3, + "axiswise_scale_D": x.variable.ndim, + "axiswise_scale_d_target": x.variable.order.axes_dict[op.axis], + "axiswise_scale_x_shape": x_shape, + "axiswise_scale_x_stride_in_y": x_stride_in_y, + }) + + name_injector = KernelNameInjector(op) + + source = template_general + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/clipped_relu.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/clipped_relu.py new file mode 100644 index 000000000..100534e6e --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/clipped_relu.py @@ -0,0 +1,54 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.operators.clipped_relu import ClippedRelu + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(clipped_relu_X)%%; + float *Y = %%LOAD_BUFFER(clipped_relu_Y)%%; + + const int N = %%LOAD_BUFFER(clipped_relu_N)%%; + const float cap = *((const float *)(& %%LOAD_BUFFER(clipped_relu_cap)%%)); + + for (int gid = 0; gid < N; gid += 1) { + float val = X[gid]; + Y[gid] = val >= 0.0F ? (val < cap ? val : cap) : 0.0F; + } +} +""" + + +# noinspection PyUnusedLocal +def clipped_relu(op: ClippedRelu, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "clipped_relu_X": x, + "clipped_relu_Y": y, + "clipped_relu_N": y.variable.size, + "clipped_relu_cap": op.parameters["cap"] + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/col2im.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/col2im.py index 318aea8a9..d98a1ed9d 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/col2im.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/col2im.py @@ -1,10 +1,10 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector from webdnn.backend.webassembly.operators.col2im import Col2Im -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.axis import Axis from webdnn.graph.order import OrderNHWC @@ -14,23 +14,23 @@ # C2, H2, W2などはすべてDeconvのinput, Convのoutputのサイズを表すために使用 template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *col = data_buffer + %%META_LOAD(col2im_col_offset)%%; - float *im = data_buffer + %%META_LOAD(col2im_im_offset)%%; + const float *col = %%LOAD_BUFFER(col2im_col)%%; + float *im = %%LOAD_BUFFER(col2im_im)%%; - const int N = %%META_LOAD(col2im_N)%%; - const int C1 = %%META_LOAD(col2im_C1)%%; - const int H1 = %%META_LOAD(col2im_H1)%%; - const int W1 = %%META_LOAD(col2im_W1)%%; - const int H2 = %%META_LOAD(col2im_H2)%%; - const int W2 = %%META_LOAD(col2im_W2)%%; - const int KH = %%META_LOAD(col2im_KH)%%; - const int KW = %%META_LOAD(col2im_KW)%%; - const int SH = %%META_LOAD(col2im_SH)%%; - const int SW = %%META_LOAD(col2im_SW)%%; - const int PH = %%META_LOAD(col2im_PH)%%; - const int PW = %%META_LOAD(col2im_PW)%%; + const int N = %%LOAD_BUFFER(col2im_N)%%; + const int C1 = %%LOAD_BUFFER(col2im_C1)%%; + const int H1 = %%LOAD_BUFFER(col2im_H1)%%; + const int W1 = %%LOAD_BUFFER(col2im_W1)%%; + const int H2 = %%LOAD_BUFFER(col2im_H2)%%; + const int W2 = %%LOAD_BUFFER(col2im_W2)%%; + const int KH = %%LOAD_BUFFER(col2im_KH)%%; + const int KW = %%LOAD_BUFFER(col2im_KW)%%; + const int SH = %%LOAD_BUFFER(col2im_SH)%%; + const int SW = %%LOAD_BUFFER(col2im_SW)%%; + const int PH = %%LOAD_BUFFER(col2im_PH)%%; + const int PW = %%LOAD_BUFFER(col2im_PW)%%; for (int gid = 0; gid < N*H1*W1*C1; gid += 1) { const int c1 = gid % C1; @@ -58,22 +58,17 @@ # noinspection PyUnusedLocal -def col2im(op: Col2Im, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - col = variables_layout[op.inputs["col"]] - im = variables_layout[op.outputs["im"]] +def col2im(op: Col2Im, memory_layout: MemoryLayout) -> List[Kernel]: + col = memory_layout[op.inputs["col"]] + im = memory_layout[op.outputs["im"]] assert col.variable.order == OrderNHWC assert im.variable.order == OrderNHWC - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - metabuffer_injector.register({ - "col2im_im_offset": im.offset, - "col2im_col_offset": col.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "col2im_im": im, + "col2im_col": col, "col2im_N": col.variable.shape_dict[Axis.N], "col2im_H2": col.variable.shape_dict[Axis.H], "col2im_W2": col.variable.shape_dict[Axis.W], @@ -88,14 +83,17 @@ def col2im(op: Col2Im, "col2im_PW": op.PW, }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("col2im", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/concat.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/concat.py index dc3638594..6700dec6b 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/concat.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/concat.py @@ -1,31 +1,28 @@ from typing import List -import numpy as np - +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.operators.concat import Concat template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - float *y = data_buffer + %%META_LOAD(concat_y_offset)%%; - const int N = %%META_LOAD(concat_N)%%; - const int D = %%META_LOAD(concat_D)%%; - const int *x_offsets = &(%%META_LOAD(concat_x_offsets)%%); - const int *y_offsets = &(%%META_LOAD(concat_y_offsets)%%); - const int *x_shapes = &(%%META_LOAD(concat_x_shapes)%%); - const int *x_strides_in_y = &(%%META_LOAD(concat_x_strides_in_y)%%); + float *y = %%LOAD_BUFFER(concat_y)%%; + const int N = %%LOAD_BUFFER(concat_N)%%; + const int D = %%LOAD_BUFFER(concat_D)%%; + const int *y_offsets = %%LOAD_BUFFER(concat_y_offsets)%%; + const int *x_shapes = %%LOAD_BUFFER(concat_x_shapes)%%; + const int *x_strides_in_y = %%LOAD_BUFFER(concat_x_strides_in_y)%%; int x_index = 0; for (int n = 0; n < N; n++) { - const float *x = data_buffer + x_offsets[n]; + const float *x = %%LOAD_BUFFER(concat_xs, n)%%; const int y_offset = y_offsets[n]; - const int *x_shape = &(x_shapes[n*D]); - const int *x_stride_in_y = &(x_strides_in_y[n*D]); + const int *x_shape = x_shapes + n * D; + const int *x_stride_in_y = x_strides_in_y + n * D; int x_size = 1; for (int d = 0; d < D; d++) { @@ -52,12 +49,9 @@ # noinspection PyUnusedLocal -def concat(op: Concat, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - xs = [variables_layout[op.inputs[f"x{str(i)}"]] for i in range(len(op.inputs))] - y = variables_layout[op.outputs["y"]] +def concat(op: Concat, memory_layout: MemoryLayout) -> List[Kernel]: + xs = [memory_layout[op.inputs[f"x{str(i)}"]] for i in range(len(op.inputs))] + y = memory_layout[op.outputs["y"]] target_axis = op.axis x_offsets = [x.offset for x in xs] @@ -82,27 +76,28 @@ def concat(op: Concat, y_offsets.append(target_axis_offset * y_strides[y.variable.order.axes_dict[target_axis]]) target_axis_offset += x.variable.shape_dict[target_axis] - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - metabuffer_injector.register({ - "concat_y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "concat_y": y, "concat_D": len(y.variable.shape), "concat_N": len(xs), - "concat_x_offsets": np.array(x_offsets, dtype=np.int32).tobytes(), - "concat_x_strides_in_y": np.array(x_strides_in_y, dtype=np.int32).tobytes(), - "concat_x_shapes": np.array(x_shapes, dtype=np.int32).tobytes(), - "concat_y_offsets": np.array(y_offsets, dtype=np.int32).tobytes(), + "concat_xs": xs, + "concat_x_strides_in_y": x_strides_in_y, + "concat_x_shapes": x_shapes, + "concat_y_offsets": y_offsets }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("concat", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/elementwise_sum.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/elementwise_sum.py index 2bfa949d4..782b404d5 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/elementwise_sum.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/elementwise_sum.py @@ -1,65 +1,55 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webassembly.inline_injector import InlineInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.graph.operators.axiswise_scale import AxiswiseScale +from webdnn.graph.operators.elementwise_sum import ElementwiseSum -template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +template_general = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X0 = data_buffer + %%META_LOAD(elementwise_sum_X0_offset)%%; - const float *X1 = data_buffer + %%META_LOAD(elementwise_sum_X1_offset)%%; - float *Y = data_buffer + %%META_LOAD(elementwise_sum_Y_offset)%%; - const int N = %%META_LOAD(elementwise_sum_N)%%; + const float *X0 = %%LOAD_BUFFER(elementwise_sum_X0)%%; + const float *X1 = %%LOAD_BUFFER(elementwise_sum_X1)%%; + float *Y = %%LOAD_BUFFER(elementwise_sum_Y)%%; + const int N = %%LOAD_BUFFER(elementwise_sum_N)%%; for (int gid = 0; gid < N; gid += 1) { float result = X0[gid] + X1[gid]; - //Y[gid] = %%CHANNELWISE_ATTACHABLE(result, c)%%; - Y[gid] = %%INLINE(result)%%; + + Y[gid] = result; } } """ -# noinspection PyUnusedLocal -def elementwise_sum(op: AxiswiseScale, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x0 = variables_layout[op.inputs["x0"]] - x1 = variables_layout[op.inputs["x1"]] - y = variables_layout[op.outputs["y"]] +def elementwise_sum(op: ElementwiseSum, memory_layout: MemoryLayout) -> List[Kernel]: + x0 = memory_layout[op.inputs["x0"]] + x1 = memory_layout[op.inputs["x1"]] + y = memory_layout[op.outputs["y"]] assert len(op.inputs) == 2, "[Webassembly] ElementwiseSum operator currently supported only 2 inputs." assert x0.variable.shape == x1.variable.shape == y.variable.shape - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - metabuffer_injector.register({ - "elementwise_sum_X0_offset": x0.offset, - "elementwise_sum_X1_offset": x1.offset, - "elementwise_sum_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "elementwise_sum_X0": x0, + "elementwise_sum_X1": x1, + "elementwise_sum_Y": y, "elementwise_sum_N": y.variable.size }) - inline_injector = InlineInjector() - if "inline_elementwise" in op.parameters: - inline_injector.delegate = op.parameters["inline_elementwise"] + name_injector = KernelNameInjector(op) - source = template - source = metabuffer_injector.inject(source) - source = inline_injector.inject(source) - func_name = util.add_canonical_suffix("elementwise_sum", source) - source = source.replace("%%FUNC_NAME%%", func_name) + source = template_general + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/elu.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/elu.py index 5d078afac..58acda479 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/elu.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/elu.py @@ -1,23 +1,22 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.operators.elu import Elu template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(relu_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(relu_Y_offset)%%; + const float *X = %%LOAD_BUFFER(relu_X)%%; + float *Y = %%LOAD_BUFFER(relu_Y)%%; - const int N = %%META_LOAD(relu_N)%%; + const int N = %%LOAD_BUFFER(relu_N)%%; for (int gid = 0; gid < N; gid += 1) { float result = X[gid]; result = result < 0.0 ? (expf(result)-1) : result; - //Y[gid] = %%ELEMENTWISE_ATTACHABLE(result)%%; Y[gid] = result; } } @@ -25,31 +24,30 @@ # noinspection PyUnusedLocal -def elu(op: Elu, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] +def elu(op: Elu, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.shape == y.variable.shape - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - metabuffer_injector.register({ - "relu_X_offset": x.offset, - "relu_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "relu_X": x, + "relu_Y": y, "relu_N": y.variable.size }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("elu", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/embedding.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/embedding.py new file mode 100644 index 000000000..2cb0d34dc --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/embedding.py @@ -0,0 +1,68 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.axis import Axis +from webdnn.graph.operators.embedding import Embedding +from webdnn.graph.order import OrderCN, OrderNT, OrderNTC + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(embedding_X)%%; + float *Y = %%LOAD_BUFFER(embedding_Y)%%; + const float *W = %%LOAD_BUFFER(embedding_W)%%; + const int vocabulary = %%LOAD_BUFFER(embedding_vocabulary)%%; + const int sequence_len = %%LOAD_BUFFER(embedding_sequence_len)%%; + const int batch_size = %%LOAD_BUFFER(embedding_batch_size)%%; + const int dim = %%LOAD_BUFFER(embedding_dim)%%; + + for (int gid = 0; gid < batch_size * sequence_len; gid += 1) { + int t = gid % sequence_len; + int batch = gid / sequence_len; + + int word = (int)X[gid]; + for (int d = 0; d < dim; d++) { + Y[gid * dim + d] = W[word * dim + d]; + } + } +} +""" + + +def embedding(op: Embedding, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + w = memory_layout[op.inputs["w"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.order == OrderNT + assert w.variable.order == OrderCN + assert y.variable.order == OrderNTC + + buffer_injector = BufferInjector() + buffer_injector.register({ + "embedding_X": x, + "embedding_Y": y, + "embedding_W": w, + "embedding_vocabulary": w.variable.shape_dict[Axis.C], + "embedding_sequence_len": x.variable.shape_dict[Axis.T], + "embedding_batch_size": x.variable.shape_dict[Axis.N], + "embedding_dim": w.variable.shape_dict[Axis.N] + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/flatten.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/flatten.py index 5fcdf861a..be0075149 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/flatten.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/flatten.py @@ -1,19 +1,18 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.kernel import GPUSize from webdnn.graph.operators.flatten import Flatten template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%% ) +void %%FUNC_NAME%%(const int * %%META_BUFFER%% ) { - const float *x = data_buffer + %%META_LOAD(flatten_x_offset)%%; - float *y = data_buffer + %%META_LOAD(flatten_y_offset)%%; + const float *x = %%LOAD_BUFFER(flatten_x)%%; + float *y = %%LOAD_BUFFER(flatten_y)%%; - const int N = %%META_LOAD(flatten_N)%%; + const int N = %%LOAD_BUFFER(flatten_N)%%; for (int gid = 0; gid < N; gid += 1) { y[gid] = x[gid]; @@ -22,30 +21,28 @@ """ -def flatten(op: Flatten, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] +def flatten(op: Flatten, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - metabuffer_injector.register({ - "flatten_x_offset": x.offset, - "flatten_y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "flatten_x": x, + "flatten_y": y, "flatten_N": y.variable.size, }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("flatten", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/hard_sigmoid.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/hard_sigmoid.py new file mode 100644 index 000000000..da11398fc --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/hard_sigmoid.py @@ -0,0 +1,58 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.operators.hard_sigmoid import HardSigmoid + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(hard_sigmoid_X)%%; + float *Y = %%LOAD_BUFFER(hard_sigmoid_Y)%%; + + const int N = %%LOAD_BUFFER(hard_sigmoid_N)%%; + + for (int gid = 0; gid < N; gid += 1) { + float val = X[gid]; + val = val * 0.2F + 0.5F; + if (val < 0.0F) { + val = 0.0F; + } else if (val > 1.0F) { + val = 1.0F; + } + Y[gid] = val; + } +} +""" + + +# noinspection PyUnusedLocal +def hard_sigmoid(op: HardSigmoid, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "hard_sigmoid_X": x, + "hard_sigmoid_Y": y, + "hard_sigmoid_N": y.variable.size + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/im2col.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/im2col.py index 3e7b5cec7..c308d2817 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/im2col.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/im2col.py @@ -1,31 +1,33 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector from webdnn.backend.webassembly.operators.im2col import Im2Col -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.axis import Axis from webdnn.graph.order import OrderNHWC, OrderCNHW template_NHWC = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *im = data_buffer + %%META_LOAD(im2col_im_offset)%%; - float *col = data_buffer + %%META_LOAD(im2col_col_offset)%%; - - const int N = %%META_LOAD(im2col_N)%%; - const int C1 = %%META_LOAD(im2col_C1)%%; - const int H1 = %%META_LOAD(im2col_H1)%%; - const int W1 = %%META_LOAD(im2col_W1)%%; - const int H2 = %%META_LOAD(im2col_H2)%%; - const int W2 = %%META_LOAD(im2col_W2)%%; - const int KH = %%META_LOAD(im2col_KH)%%; - const int KW = %%META_LOAD(im2col_KW)%%; - const int SH = %%META_LOAD(im2col_SH)%%; - const int SW = %%META_LOAD(im2col_SW)%%; - const int PH = %%META_LOAD(im2col_PH)%%; - const int PW = %%META_LOAD(im2col_PW)%%; + const float *im = %%LOAD_BUFFER(im2col_im)%%; + float *col = %%LOAD_BUFFER(im2col_col)%%; + + const int N = %%LOAD_BUFFER(im2col_N)%%; + const int C1 = %%LOAD_BUFFER(im2col_C1)%%; + const int H1 = %%LOAD_BUFFER(im2col_H1)%%; + const int W1 = %%LOAD_BUFFER(im2col_W1)%%; + const int H2 = %%LOAD_BUFFER(im2col_H2)%%; + const int W2 = %%LOAD_BUFFER(im2col_W2)%%; + const int KH = %%LOAD_BUFFER(im2col_KH)%%; + const int KW = %%LOAD_BUFFER(im2col_KW)%%; + const int DH = %%LOAD_BUFFER(im2col_DH)%%; + const int DW = %%LOAD_BUFFER(im2col_DW)%%; + const int SH = %%LOAD_BUFFER(im2col_SH)%%; + const int SW = %%LOAD_BUFFER(im2col_SW)%%; + const int PH = %%LOAD_BUFFER(im2col_PH)%%; + const int PW = %%LOAD_BUFFER(im2col_PW)%%; for (int gid = 0; gid < N*H2*W2*KH*KW*C1; gid += 1) { const int c1 = gid % C1; @@ -35,8 +37,8 @@ const int h2 = gid / C1 / KW / KH / W2 % H2; const int n = gid / C1 / KW / KH / W2 / H2; - const int h1 = h2 * SH - PH + kh; - const int w1 = w2 * SW - PW + kw; + const int h1 = h2 * SH - PH + kh * DH; + const int w1 = w2 * SW - PW + kw * DW; col[gid] = (h1 < 0 || h1 >= H1 || w1 < 0 || w1 >= W1) ? 0 : im[((n*H1+h1)*W1+w1)*C1+c1]; } @@ -44,23 +46,25 @@ """ template_CNHW = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *im = data_buffer + %%META_LOAD(im2col_im_offset)%%; - float *col = data_buffer + %%META_LOAD(im2col_col_offset)%%; - - const int N = %%META_LOAD(im2col_N)%%; - const int C1 = %%META_LOAD(im2col_C1)%%; - const int H1 = %%META_LOAD(im2col_H1)%%; - const int W1 = %%META_LOAD(im2col_W1)%%; - const int H2 = %%META_LOAD(im2col_H2)%%; - const int W2 = %%META_LOAD(im2col_W2)%%; - const int KH = %%META_LOAD(im2col_KH)%%; - const int KW = %%META_LOAD(im2col_KW)%%; - const int SH = %%META_LOAD(im2col_SH)%%; - const int SW = %%META_LOAD(im2col_SW)%%; - const int PH = %%META_LOAD(im2col_PH)%%; - const int PW = %%META_LOAD(im2col_PW)%%; + const float *im = %%LOAD_BUFFER(im2col_im)%%; + float *col = %%LOAD_BUFFER(im2col_col)%%; + + const int N = %%LOAD_BUFFER(im2col_N)%%; + const int C1 = %%LOAD_BUFFER(im2col_C1)%%; + const int H1 = %%LOAD_BUFFER(im2col_H1)%%; + const int W1 = %%LOAD_BUFFER(im2col_W1)%%; + const int H2 = %%LOAD_BUFFER(im2col_H2)%%; + const int W2 = %%LOAD_BUFFER(im2col_W2)%%; + const int KH = %%LOAD_BUFFER(im2col_KH)%%; + const int KW = %%LOAD_BUFFER(im2col_KW)%%; + const int DH = %%LOAD_BUFFER(im2col_DH)%%; + const int DW = %%LOAD_BUFFER(im2col_DW)%%; + const int SH = %%LOAD_BUFFER(im2col_SH)%%; + const int SW = %%LOAD_BUFFER(im2col_SW)%%; + const int PH = %%LOAD_BUFFER(im2col_PH)%%; + const int PW = %%LOAD_BUFFER(im2col_PW)%%; for (int gid = 0; gid < N*H2*W2*KH*KW*C1; gid += 1) { const int w2 = gid % W2; @@ -70,8 +74,8 @@ const int kw = gid / W2 / H2 / N / C1 % KW; const int kh = gid / W2 / H2 / N / C1 / KW; - const int h1 = h2 * SH - PH + kh; - const int w1 = w2 * SW - PW + kw; + const int h1 = h2 * SH - PH + kh * DH; + const int w1 = w2 * SW - PW + kw * DW; col[gid] = (h1 < 0 || h1 >= H1 || w1 < 0 || w1 >= W1) ? 0 : im[((n*H1+h1)*W1+w1)*C1+c1]; } @@ -80,22 +84,17 @@ # noinspection PyUnusedLocal -def im2col(op: Im2Col, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - im = variables_layout[op.inputs["im"]] - col = variables_layout[op.outputs["col"]] +def im2col(op: Im2Col, memory_layout: MemoryLayout) -> List[Kernel]: + im = memory_layout[op.inputs["im"]] + col = memory_layout[op.outputs["col"]] assert im.variable.order == OrderNHWC assert col.variable.order == OrderNHWC or col.variable.order == OrderCNHW - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - metabuffer_injector.register({ - "im2col_im_offset": im.offset, - "im2col_col_offset": col.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "im2col_im": im, + "im2col_col": col, "im2col_N": col.variable.shape_dict[Axis.N], "im2col_C1": im.variable.shape_dict[Axis.C], "im2col_H1": im.variable.shape_dict[Axis.H], @@ -104,21 +103,25 @@ def im2col(op: Im2Col, "im2col_W2": col.variable.shape_dict[Axis.W], "im2col_KH": op.KH, "im2col_KW": op.KW, + "im2col_DH": op.DH, + "im2col_DW": op.DW, "im2col_SH": op.SH, "im2col_SW": op.SW, "im2col_PH": op.PH, "im2col_PW": op.PW, }) + name_injector = KernelNameInjector(op) + source = template_CNHW if col.variable.order == OrderCNHW else template_NHWC - source = metabuffer_injector.inject(source) - func_name = util.add_canonical_suffix("im2col", source) - source = source.replace("%%FUNC_NAME%%", func_name) + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/leaky_relu.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/leaky_relu.py new file mode 100644 index 000000000..f7224330b --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/leaky_relu.py @@ -0,0 +1,54 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.operators.leaky_relu import LeakyRelu + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(leaky_relu_X)%%; + float *Y = %%LOAD_BUFFER(leaky_relu_Y)%%; + + const int N = %%LOAD_BUFFER(leaky_relu_N)%%; + const float slope = *((const float *)(& %%LOAD_BUFFER(leaky_relu_slope)%%)); + + for (int gid = 0; gid < N; gid += 1) { + float val = X[gid]; + Y[gid] = val >= 0.0F ? val : val * slope; + } +} +""" + + +# noinspection PyUnusedLocal +def leaky_relu(op: LeakyRelu, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "leaky_relu_X": x, + "leaky_relu_Y": y, + "leaky_relu_N": y.variable.size, + "leaky_relu_slope": op.parameters["slope"] + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/linear.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/linear.py index b367261ef..3ea795fc2 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/linear.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/linear.py @@ -1,25 +1,23 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.axis import Axis from webdnn.graph.operators.linear import Linear from webdnn.graph.order import OrderNC, OrderNHWC, OrderCN, OrderHWCN template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(linear_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(linear_Y_offset)%%; - const float *W = weight_buffer + %%META_LOAD(linear_W_offset)%%; - const int M = %%META_LOAD(linear_M)%%; - const int N = %%META_LOAD(linear_N)%%; - const int K = %%META_LOAD(linear_K)%%; + const float *X = %%LOAD_BUFFER(linear_X)%%; + float *Y = %%LOAD_BUFFER(linear_Y)%%; + const float *W = %%LOAD_BUFFER(linear_W)%%; + const int M = %%LOAD_BUFFER(linear_M)%%; + const int N = %%LOAD_BUFFER(linear_N)%%; + const int K = %%LOAD_BUFFER(linear_K)%%; - //%%INITIALIZER_ATTACHABLE_PLACEHOLDER%% - for (int gid = 0; gid < M * N; gid += 1) { int n = gid % N; int m = gid / N; @@ -29,45 +27,43 @@ sum += X[m * K + k] * W[k * N + n]; } - //Y[gid] = %%CHANNELWISE_ATTACHABLE(sum, n)%%; Y[gid] = sum; } } """ -def linear(op: Linear, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - w = constants_layout[op.inputs["w"]] - y = variables_layout[op.outputs["y"]] +def linear(op: Linear, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + w = memory_layout[op.inputs["w"]] + y = memory_layout[op.outputs["y"]] assert x.variable.order == OrderNC or x.variable.order == OrderNHWC assert w.variable.order == OrderCN or w.variable.order == OrderHWCN assert y.variable.order == OrderNC or y.variable.order == OrderNHWC assert w.variable.ndim == x.variable.ndim - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - metabuffer_injector.register({ - "linear_X_offset": x.offset, - "linear_Y_offset": y.offset, - "linear_W_offset": w.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "linear_X": x, + "linear_Y": y, + "linear_W": w, "linear_M": y.variable.shape_dict[Axis.N], "linear_N": y.variable.size // y.variable.shape_dict[Axis.N], "linear_K": x.variable.size // x.variable.shape_dict[Axis.N], }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("linear", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/local_response_normalization.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/local_response_normalization.py index 160e957d0..73d33a6d8 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/local_response_normalization.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/local_response_normalization.py @@ -1,29 +1,27 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.axis import Axis from webdnn.graph.operators.local_response_normalization import LocalResponseNormalization from webdnn.graph.order import OrderNHWC template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(local_response_normalization_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(local_response_normalization_Y_offset)%%; - const int N = %%META_LOAD(local_response_normalization_N)%%; - const int H = %%META_LOAD(local_response_normalization_H)%%; - const int W = %%META_LOAD(local_response_normalization_W)%%; - const int C = %%META_LOAD(local_response_normalization_C)%%; - const int Phalfn = %%META_LOAD(local_response_normalization_param_half_n)%%; - const float Pk = *((const float *)(& %%META_LOAD(local_response_normalization_param_k)%%)); - const float Palpha = *((const float *)(& %%META_LOAD(local_response_normalization_param_alpha)%%)); - const float Pmbeta = *((const float *)(& %%META_LOAD(local_response_normalization_param_minus_beta)%%)); + const float *X = %%LOAD_BUFFER(local_response_normalization_X)%%; + float *Y = %%LOAD_BUFFER(local_response_normalization_Y)%%; + const int N = %%LOAD_BUFFER(local_response_normalization_N)%%; + const int H = %%LOAD_BUFFER(local_response_normalization_H)%%; + const int W = %%LOAD_BUFFER(local_response_normalization_W)%%; + const int C = %%LOAD_BUFFER(local_response_normalization_C)%%; + const int Phalfn = %%LOAD_BUFFER(local_response_normalization_param_half_n)%%; + const float Pk = *((const float *)(& %%LOAD_BUFFER(local_response_normalization_param_k)%%)); + const float Palpha = *((const float *)(& %%LOAD_BUFFER(local_response_normalization_param_alpha)%%)); + const float Pmbeta = *((const float *)(& %%LOAD_BUFFER(local_response_normalization_param_minus_beta)%%)); - //%%INITIALIZER_ATTACHABLE_PLACEHOLDER%% - for (int gid = 0; gid < N * H * W * C; gid += 1) { const int c = gid % C; const int w = gid / C % W; @@ -48,7 +46,6 @@ float scale = powf(sq_sum * Palpha + Pk, Pmbeta); float v = X[gid] * scale; - //Y[gid] = %%CHANNELWISE_ATTACHABLE(v, n)%%; Y[gid] = v; } } @@ -56,22 +53,17 @@ # noinspection PyUnusedLocal -def local_response_normalization(op: LocalResponseNormalization, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] +def local_response_normalization(op: LocalResponseNormalization, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.order == OrderNHWC assert y.variable.order == OrderNHWC - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - metabuffer_injector.register({ - "local_response_normalization_X_offset": x.offset, - "local_response_normalization_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "local_response_normalization_X": x, + "local_response_normalization_Y": y, "local_response_normalization_N": x.variable.shape_dict[Axis.N], "local_response_normalization_H": x.variable.shape_dict[Axis.H], "local_response_normalization_W": x.variable.shape_dict[Axis.W], @@ -82,14 +74,17 @@ def local_response_normalization(op: LocalResponseNormalization, "local_response_normalization_param_minus_beta": float(-op.parameters["beta"]) }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("local_response_normalization", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/lstm.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/lstm.py new file mode 100644 index 000000000..8b0bf4b95 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/lstm.py @@ -0,0 +1,221 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.axis import Axis +from webdnn.graph.operators.lstm import LSTM +from webdnn.graph.order import OrderNC, OrderCN, OrderNTC + +template = """ +#ifndef INCLUDE_EIGEN +#define INCLUDE_EIGEN +#include +#endif + +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ +%%DEFINE_SEQUENCE_OUTPUT%% + const float *X = %%LOAD_BUFFER(lstm_X)%%; + float *Y = %%LOAD_BUFFER(lstm_Y)%%; + float *mem_c = %%LOAD_BUFFER(lstm_final_c)%%; + float *W_input = %%LOAD_BUFFER(lstm_W_input)%%; + float *W_hidden = %%LOAD_BUFFER(lstm_W_hidden)%%; + const int input_dim = %%LOAD_BUFFER(lstm_input_dim)%%; + const int sequence_len = %%LOAD_BUFFER(lstm_sequence_len)%%; + const int batch_size = %%LOAD_BUFFER(lstm_batch_size)%%; + const int hidden_dim = %%LOAD_BUFFER(lstm_hidden_dim)%%; + const int hidden_dim4 = hidden_dim * 4; + const int ofs_i = 0; + const int ofs_f = hidden_dim * 1; + const int ofs_c = hidden_dim * 2; + const int ofs_o = hidden_dim * 3; + %%BIAS_INITIALIZER%% + + auto activation = [](float x) { + %%ACTIVATION_CORE%% + }; + + auto recurrent_activation = [](float x) { + %%RECURRENT_ACTIVATION_CORE%% + }; + + %%INITIAL_C_COPIER%% + float *mem_h = new float[hidden_dim * batch_size](); + %%INITIAL_H_COPIER%% + float *mem_v = new float[hidden_dim4 * batch_size](); // i, f, c, o + float *mem_x_t = new float[input_dim * batch_size](); + Eigen::Map > mat_v(mem_v, batch_size, hidden_dim4); + Eigen::Map > mat_h(mem_h, batch_size, hidden_dim); + Eigen::Map > mat_x_t(mem_x_t, batch_size, input_dim); + Eigen::Map > mat_w_input(W_input, input_dim, hidden_dim4); + Eigen::Map > mat_w_hidden(W_hidden, hidden_dim, hidden_dim4); + + for (int t = 0; t < sequence_len; t++) { + // copy x of current time + for (int n = 0; n < batch_size; n++) { + for (int dim = 0; dim < input_dim; dim++) { + mem_x_t[dim + n * input_dim] = X[(n * sequence_len + t) * input_dim + dim]; + } + } + + mat_v.noalias() = mat_x_t * mat_w_input + mat_h * mat_w_hidden; + %%BIAS_APPLIER%% + + for (int n = 0; n < batch_size; n++) { + // update c, h + for (int dim = 0; dim < hidden_dim; dim++) { + float val_i = mem_v[dim + ofs_i + n * hidden_dim4]; + float val_f = mem_v[dim + ofs_f + n * hidden_dim4]; + float val_c = mem_v[dim + ofs_c + n * hidden_dim4]; + float val_o = mem_v[dim + ofs_o + n * hidden_dim4]; + val_i = recurrent_activation(val_i); + val_f = recurrent_activation(val_f); + float val_last_c = mem_c[dim + n * hidden_dim]; + val_c = activation(val_c) * val_i + val_last_c * val_f; + mem_c[dim + n * hidden_dim] = val_c; + mem_h[dim + n * hidden_dim] = activation(val_c) * recurrent_activation(val_o); + } + } + + //write output on sequence +#ifdef SEQUENCE_OUTPUT + for (int n = 0; n < batch_size; n++) { + for (int dim = 0; dim < hidden_dim; dim++) { + Y[(n * sequence_len + t) * hidden_dim + dim] = mem_h[n * hidden_dim + dim]; + } + } +#endif + } + + // write output +#ifndef SEQUENCE_OUTPUT + for (int i = 0; i < batch_size * hidden_dim; i++) { + Y[i] = mem_h[i]; + } +#endif + + delete[] mem_h; + delete[] mem_v; + delete[] mem_x_t; +#undef SEQUENCE_OUTPUT +} +""" + + +def lstm(op: LSTM, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + w_input = memory_layout[op.inputs["w_input"]] + w_hidden = memory_layout[op.inputs["w_hidden"]] + y = memory_layout[op.outputs["y"]] + final_c = memory_layout[op.outputs["final_c"]] + + assert x.variable.order == OrderNTC + assert w_input.variable.order == OrderCN + assert w_hidden.variable.order == OrderCN + if op.parameters["return_sequences"]: + assert y.variable.order == OrderNTC + else: + assert y.variable.order == OrderNC + assert final_c.variable.order == OrderNC + + # W is for updating i, f, c, o + hidden_dim = w_hidden.variable.shape_dict[Axis.C] + + buffer_injector_items = { + "lstm_X": x, + "lstm_Y": y, + "lstm_final_c": final_c, + "lstm_W_input": w_input, + "lstm_W_hidden": w_hidden, + "lstm_input_dim": x.variable.shape_dict[Axis.C], + "lstm_sequence_len": x.variable.shape_dict[Axis.T], + "lstm_batch_size": x.variable.shape_dict[Axis.N], + "lstm_hidden_dim": hidden_dim + } + + source = template + if op.parameters["return_sequences"]: + source = source.replace("%%DEFINE_SEQUENCE_OUTPUT%%", "#define SEQUENCE_OUTPUT") + else: + source = source.replace("%%DEFINE_SEQUENCE_OUTPUT%%", "") + + if op.parameters["use_bias"]: + b = memory_layout[op.inputs["b"]] + buffer_injector_items["lstm_b"] = b + source = source.replace("%%BIAS_INITIALIZER%%", "float *b = %%LOAD_BUFFER(lstm_b)%%;\nEigen::Map vec_b(b, hidden_dim4);") + source = source.replace("%%BIAS_APPLIER%%", "mat_v.rowwise() += vec_b;") + else: + source = source.replace("%%BIAS_INITIALIZER%%", "") + source = source.replace("%%BIAS_APPLIER%%", "") + + if op.parameters["use_initial_c"]: + initial_c = memory_layout[op.inputs["initial_c"]] + buffer_injector_items["lstm_initial_c"] = initial_c + source = source.replace("%%INITIAL_C_COPIER%%", """ + const float *initial_c = %%LOAD_BUFFER(lstm_initial_c)%%; + for (int i = 0; i < hidden_dim * batch_size; i++) { + mem_c[i] = initial_c[i]; + } + """) + else: + source = source.replace("%%INITIAL_C_COPIER%%", """ + for (int i = 0; i < hidden_dim * batch_size; i++) { + mem_c[i] = 0.0F; + } + """) + + if op.parameters["use_initial_h"]: + initial_h = memory_layout[op.inputs["initial_h"]] + buffer_injector_items["lstm_initial_h"] = initial_h + source = source.replace("%%INITIAL_H_COPIER%%", """ + const float *initial_h = %%LOAD_BUFFER(lstm_initial_h)%%; + for (int i = 0; i < hidden_dim * batch_size; i++) { + mem_h[i] = initial_h[i]; + } + """) + else: + source = source.replace("%%INITIAL_H_COPIER%%", "") + + if op.parameters["activation"] == "tanh": + source = source.replace("%%ACTIVATION_CORE%%", """ + return tanhf(x); + """) + else: + raise NotImplementedError + + if op.parameters["recurrent_activation"] == "hard_sigmoid": + source = source.replace("%%RECURRENT_ACTIVATION_CORE%%", """ + x = x * 0.2F + 0.5F; + if (x < 0.0F) { + x = 0.0F; + } else if (x > 1.0F) { + x = 1.0F; + } + return x; + """) + elif op.parameters["recurrent_activation"] == "sigmoid": + source = source.replace("%%RECURRENT_ACTIVATION_CORE%%", """ + x = 1.0F / (1.0 + expf(-x)); + return x; + """) + else: + raise NotImplementedError + + buffer_injector = BufferInjector() + buffer_injector.register(buffer_injector_items) + + name_injector = KernelNameInjector(op) + + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/max_pooling_2d.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/max_pooling_2d.py index e0d73f060..8c31f22a7 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/max_pooling_2d.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/max_pooling_2d.py @@ -1,29 +1,30 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.axis import Axis from webdnn.graph.operators.max_pooling_2d import MaxPooling2D from webdnn.graph.order import OrderNHWC template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(max_pooling_2d_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(max_pooling_2d_Y_offset)%%; - const int N = %%META_LOAD(max_pooling_2d_N)%%; - const int H1 = %%META_LOAD(max_pooling_2d_H1)%%; - const int W1 = %%META_LOAD(max_pooling_2d_W1)%%; - const int C = %%META_LOAD(max_pooling_2d_C)%%; - const int H2 = %%META_LOAD(max_pooling_2d_H2)%%; - const int W2 = %%META_LOAD(max_pooling_2d_W2)%%; - const int K = %%META_LOAD(max_pooling_2d_K)%%; - const int S = %%META_LOAD(max_pooling_2d_S)%%; - const int P = %%META_LOAD(max_pooling_2d_P)%%; - - //%%INITIALIZER_ATTACHABLE_PLACEHOLDER%% + const float *X = %%LOAD_BUFFER(max_pooling_2d_X)%%; + float *Y = %%LOAD_BUFFER(max_pooling_2d_Y)%%; + const int N = %%LOAD_BUFFER(max_pooling_2d_N)%%; + const int H1 = %%LOAD_BUFFER(max_pooling_2d_H1)%%; + const int W1 = %%LOAD_BUFFER(max_pooling_2d_W1)%%; + const int C = %%LOAD_BUFFER(max_pooling_2d_C)%%; + const int H2 = %%LOAD_BUFFER(max_pooling_2d_H2)%%; + const int W2 = %%LOAD_BUFFER(max_pooling_2d_W2)%%; + const int KH = %%LOAD_BUFFER(max_pooling_2d_KH)%%; + const int KW = %%LOAD_BUFFER(max_pooling_2d_KW)%%; + const int SH = %%LOAD_BUFFER(max_pooling_2d_SH)%%; + const int SW = %%LOAD_BUFFER(max_pooling_2d_SW)%%; + const int PH = %%LOAD_BUFFER(max_pooling_2d_PH)%%; + const int PW = %%LOAD_BUFFER(max_pooling_2d_PW)%%; for (int gid = 0; gid < N * H2 * W2 * C; gid += 1) { const int c = gid % C; @@ -32,19 +33,18 @@ const int n = gid / C / W2 / H2; float v = -1e7; - for (int kh = 0; kh < K; kh++) { - const int h1 = h2 * S - P + kh; + for (int kh = 0; kh < KH; kh++) { + const int h1 = h2 * SH - PH + kh; if (h1 < 0 || h1 >= H1) continue; - for (int kw = 0; kw < K; kw++) { - const int w1 = w2 * S - P + kw; + for (int kw = 0; kw < KW; kw++) { + const int w1 = w2 * SW - PW + kw; if (w1 < 0 || w1 >= W1) continue; v = v > X[((n * H1 + h1) * W1 + w1) * C + c] ? v : X[((n * H1 + h1) * W1 + w1) * C + c]; } } - //Y[gid] = %%CHANNELWISE_ATTACHABLE(v, n)%%; Y[gid] = v; } } @@ -52,41 +52,42 @@ # noinspection PyUnusedLocal -def max_pooling_2d(op: MaxPooling2D, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] +def max_pooling_2d(op: MaxPooling2D, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.order == OrderNHWC assert y.variable.order == OrderNHWC - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - metabuffer_injector.register({ - "max_pooling_2d_X_offset": x.offset, - "max_pooling_2d_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "max_pooling_2d_X": x, + "max_pooling_2d_Y": y, "max_pooling_2d_N": x.variable.shape_dict[Axis.N], "max_pooling_2d_H1": x.variable.shape_dict[Axis.H], "max_pooling_2d_W1": x.variable.shape_dict[Axis.W], "max_pooling_2d_C": x.variable.shape_dict[Axis.C], "max_pooling_2d_H2": y.variable.shape_dict[Axis.H], "max_pooling_2d_W2": y.variable.shape_dict[Axis.W], - "max_pooling_2d_K": op.parameters["ksize"][0], - "max_pooling_2d_S": op.parameters["stride"][0], - "max_pooling_2d_P": op.parameters["padding"][0], + "max_pooling_2d_KH": op.parameters["ksize"][0], + "max_pooling_2d_KW": op.parameters["ksize"][1], + "max_pooling_2d_SH": op.parameters["stride"][0], + "max_pooling_2d_SW": op.parameters["stride"][1], + "max_pooling_2d_PH": op.parameters["padding"][0], + "max_pooling_2d_PW": op.parameters["padding"][1], }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("max_pooling_2d", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/reinterpret_axis.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/reinterpret_axis.py new file mode 100644 index 000000000..4785eb3ce --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/reinterpret_axis.py @@ -0,0 +1,52 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.operators.reinterpret_axis import ReinterpretAxis + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%% ) +{ + const float *x = %%LOAD_BUFFER(reinterpret_axis_x)%%; + float *y = %%LOAD_BUFFER(reinterpret_axis_y)%%; + + const int N = %%LOAD_BUFFER(reinterpret_axis_N)%%; + + for (int gid = 0; gid < N; gid += 1) { + y[gid] = x[gid]; + } +} +""" + + +def reinterpret_axis(op: ReinterpretAxis, memory_layout: MemoryLayout) -> List[Kernel]: + # Operation without need for transposition is currently supported + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.order == op.parameters["in_order"] + assert y.variable.order == op.parameters["out_order"] + + buffer_injector = BufferInjector() + buffer_injector.register({ + "reinterpret_axis_x": x, + "reinterpret_axis_y": y, + "reinterpret_axis_N": y.variable.size, + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/relu.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/relu.py index fd701a79f..faf947f7c 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/relu.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/relu.py @@ -1,23 +1,23 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.operators.relu import Relu template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(relu_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(relu_Y_offset)%%; + const float *X = %%LOAD_BUFFER(relu_X)%%; + float *Y = %%LOAD_BUFFER(relu_Y)%%; - const int N = %%META_LOAD(relu_N)%%; + const int N = %%LOAD_BUFFER(relu_N)%%; for (int gid = 0; gid < N; gid += 1) { float result = X[gid]; - result = result < 0.0 ? 0.0 : result; - //Y[gid] = %%ELEMENTWISE_ATTACHABLE(result)%%; + result = result < 0.0 ? 0.0 : result; + Y[gid] = result; } } @@ -25,31 +25,30 @@ # noinspection PyUnusedLocal -def relu(op: Relu, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] +def relu(op: Relu, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.shape == y.variable.shape - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - metabuffer_injector.register({ - "relu_X_offset": x.offset, - "relu_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "relu_X": x, + "relu_Y": y, "relu_N": y.variable.size }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("relu", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/reshape.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/reshape.py new file mode 100644 index 000000000..26539a5d7 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/reshape.py @@ -0,0 +1,54 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.operators.reshape import Reshape +from webdnn.util.misc import mul + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%% ) +{ + const float *x = %%LOAD_BUFFER(reshape_x)%%; + float *y = %%LOAD_BUFFER(reshape_y)%%; + + const int N = %%LOAD_BUFFER(reshape_N)%%; + + for (int gid = 0; gid < N; gid += 1) { + y[gid] = x[gid]; + } +} +""" + + +def reshape(op: Reshape, memory_layout: MemoryLayout) -> List[Kernel]: + # Operation without need for transposition is currently supported + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.order == op.parameters["in_order"] + assert y.variable.order == op.parameters["out_order"] + assert y.variable.size == mul(op.parameters["out_shape"]) + + buffer_injector = BufferInjector() + buffer_injector.register({ + "reshape_x": x, + "reshape_y": y, + "reshape_N": y.variable.size, + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/scalar_affine.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/scalar_affine.py index 98fdf1aef..8f68b79e8 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/scalar_affine.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/scalar_affine.py @@ -1,25 +1,25 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.operators.scalar_affine import ScalarAffine template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(affine_transform_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(affine_transform_Y_offset)%%; + const float *X = %%LOAD_BUFFER(affine_transform_X)%%; + float *Y = %%LOAD_BUFFER(affine_transform_Y)%%; - const float scale = *((const float *)(& %%META_LOAD(affine_transform_scale)%%)); - const float bias = *((const float *)(& %%META_LOAD(affine_transform_bias)%%)); - const int N = %%META_LOAD(affine_transform_N)%%; + const float scale = *((const float *)(& %%LOAD_BUFFER(affine_transform_scale)%%)); + const float bias = *((const float *)(& %%LOAD_BUFFER(affine_transform_bias)%%)); + const int N = %%LOAD_BUFFER(affine_transform_N)%%; for (int gid = 0; gid < N; gid += 1) { float result = X[gid]; result = result * scale + bias; - //Y[gid] = %%ELEMENTWISE_ATTACHABLE(result)%%; + Y[gid] = result; } } @@ -27,32 +27,31 @@ # noinspection PyUnusedLocal -def scalar_affine(op: ScalarAffine, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] +def scalar_affine(op: ScalarAffine, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.shape == y.variable.shape - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - metabuffer_injector.register({ - "affine_transform_X_offset": x.offset, - "affine_transform_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "affine_transform_X": x, + "affine_transform_Y": y, "affine_transform_N": y.variable.size, - "affine_transform_scale": op.scale, - "affine_transform_bias": op.bias + "affine_transform_scale": float(op.scale), + "affine_transform_bias": float(op.bias) }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("scalar_affine", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/sgemm.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/sgemm.py index 3e5bc7f61..6272b1cc0 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/sgemm.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/sgemm.py @@ -1,23 +1,23 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector from webdnn.backend.webassembly.operators.sgemm import Sgemm -from webdnn.backend.webgpu.allocator import MemoryLayout def generate_template(transpose_A, transpose_B): return """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - float *A = data_buffer + %%META_LOAD(sgemm_A_offset, 1)%%; - float *B = weight_buffer + %%META_LOAD(sgemm_B_offset, 1)%%; - float *C = data_buffer + %%META_LOAD(sgemm_C_offset, 1)%%; + float *A = %%LOAD_BUFFER(sgemm_A)%%; + float *B = %%LOAD_BUFFER(sgemm_B)%%; + float *C = %%LOAD_BUFFER(sgemm_C)%%; - const int M = %%META_LOAD(sgemm_M, 1)%%; - const int N = %%META_LOAD(sgemm_N, 1)%%; - const int K = %%META_LOAD(sgemm_K, 1)%%; + const int M = %%LOAD_BUFFER(sgemm_M)%%; + const int N = %%LOAD_BUFFER(sgemm_N)%%; + const int K = %%LOAD_BUFFER(sgemm_K)%%; const int a_stride_k = %%A_STRIDE_K%%; const int a_stride_mn = %%A_STRIDE_MN%%; @@ -50,44 +50,33 @@ def generate_template_eigen(transpose_A, transpose_B, M, N, K): #include #endif -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - float *A = data_buffer + %%META_LOAD(sgemm_A_offset, 1)%%; - float *B = weight_buffer + %%META_LOAD(sgemm_B_offset, 1)%%; - float *C = data_buffer + %%META_LOAD(sgemm_C_offset, 1)%%; + float *A = %%LOAD_BUFFER(sgemm_A)%%; + float *B = %%LOAD_BUFFER(sgemm_B)%%; + float *C = %%LOAD_BUFFER(sgemm_C)%%; - Eigen::Map > a_mat(A, %%M%%, %%K%%); - Eigen::Map > b_mat(B, %%K%%, %%N%%); - Eigen::Map > c_mat(C, %%M%%, %%N%%); - //Eigen::Map > a_mat(A, %%M%%, %%K%%); - //Eigen::Map > b_mat(B, %%K%%, %%N%%); - //Eigen::Map > c_mat(C, %%M%%, %%N%%); + Eigen::Map > a_mat(A, %%LOAD_BUFFER(sgemm_M)%%, %%LOAD_BUFFER(sgemm_K)%%); + Eigen::Map > b_mat(B, %%LOAD_BUFFER(sgemm_K)%%, %%LOAD_BUFFER(sgemm_N)%%); + Eigen::Map > c_mat(C, %%LOAD_BUFFER(sgemm_M)%%, %%LOAD_BUFFER(sgemm_N)%%); c_mat.noalias() = a_mat * b_mat; } """ \ .replace("%%A_MAJOR%%", "RowMajor" if transpose_A else "ColMajor") \ - .replace("%%B_MAJOR%%", "RowMajor" if transpose_B else "ColMajor") \ - .replace("%%M%%", str(M)) \ - .replace("%%N%%", str(N)) \ - .replace("%%K%%", str(K)) - - -def sgemm(op: Sgemm, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - A = variables_layout[op.inputs["A"]] if op.inputs["A"] in variables_layout else constants_layout[op.inputs["A"]] - B = variables_layout[op.inputs["B"]] if op.inputs["B"] in variables_layout else constants_layout[op.inputs["B"]] - C = variables_layout[op.outputs["C"]] - - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - - metabuffer_injector.register({ - "sgemm_A_offset": A.offset, - "sgemm_B_offset": B.offset, - "sgemm_C_offset": C.offset, + .replace("%%B_MAJOR%%", "RowMajor" if transpose_B else "ColMajor") + + +def sgemm(op: Sgemm, memory_layout: MemoryLayout) -> List[Kernel]: + A = memory_layout[op.inputs["A"]] + B = memory_layout[op.inputs["B"]] + C = memory_layout[op.outputs["C"]] + + buffer_injector = BufferInjector() + buffer_injector.register({ + "sgemm_A": A, + "sgemm_B": B, + "sgemm_C": C, "sgemm_M": op.M, "sgemm_N": op.N, "sgemm_K": op.K @@ -95,33 +84,33 @@ def sgemm(op: Sgemm, if op.parameters["eigen"]: source = generate_template_eigen(op.transpose_A, op.transpose_B, op.M, op.N, op.K) - - metabuffer_injector.register({ - "sgemm_A_offset": A.offset, - "sgemm_B_offset": B.offset, - "sgemm_C_offset": C.offset + buffer_injector.register({ + "sgemm_A": A, + "sgemm_B": B, + "sgemm_C": C }) + else: source = generate_template(op.transpose_A, op.transpose_B) - - metabuffer_injector.register({ - "sgemm_A_offset": A.offset, - "sgemm_B_offset": B.offset, - "sgemm_C_offset": C.offset, + buffer_injector.register({ + "sgemm_A": A, + "sgemm_B": B, + "sgemm_C": C, "sgemm_M": op.M, "sgemm_N": op.N, "sgemm_K": op.K }) - source = metabuffer_injector.inject(source) - func_name = util.add_canonical_suffix("sgemm", source) - source = source.replace("%%FUNC_NAME%%", func_name) + + name_injector = KernelNameInjector(op) + + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - { - func_name: source - }, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/sigmoid.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/sigmoid.py new file mode 100644 index 000000000..e3d2967ee --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/sigmoid.py @@ -0,0 +1,51 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.operators.sigmoid import Sigmoid + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(sigmoid_X)%%; + float *Y = %%LOAD_BUFFER(sigmoid_Y)%%; + + const int N = %%LOAD_BUFFER(sigmoid_N)%%; + + for (int gid = 0; gid < N; gid += 1) { + Y[gid] = 1.0F / (1.0F + expf(-X[gid])); + } +} +""" + + +# noinspection PyUnusedLocal +def sigmoid(op: Sigmoid, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "sigmoid_X": x, + "sigmoid_Y": y, + "sigmoid_N": y.variable.size + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/softmax.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/softmax.py new file mode 100644 index 000000000..99899454a --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/softmax.py @@ -0,0 +1,74 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.axis import Axis +from webdnn.graph.operators.softmax import Softmax + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(softmax_X)%%; + float *Y = %%LOAD_BUFFER(softmax_Y)%%; + const int N = %%LOAD_BUFFER(softmax_N)%%; + const int C = %%LOAD_BUFFER(softmax_C)%%; + + for (int n = 0; n < N; n++) { + float set_max = X[n * C]; + for (int c = 0; c < C; c++) { + float val = X[n * C + c]; + if (val > set_max) { + set_max = val; + } + } + + float sum_exp = 0.0F; + for (int c = 0; c < C; c++) { + float val = X[n * C + c]; + float exp_x = expf(val - set_max); + sum_exp += exp_x; + Y[n * C + c] = exp_x; + } + + for (int c = 0; c < C; c++) { + Y[n * C + c] /= sum_exp; + } + } +} +""" + + +def softmax(op: Softmax, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert y.variable.order == x.variable.order + assert y.variable.shape == x.variable.shape + + axis = op.parameters["axis"] + assert axis == x.variable.order.axes[-1], "[Webassembly] Softmax supports only for aggregating last axis." + + buffer_injector = BufferInjector() + buffer_injector.register({ + "softmax_X": x, + "softmax_Y": y, + "softmax_N": y.variable.size // y.variable.shape_dict[axis], + "softmax_C": y.variable.shape_dict[axis], + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/softplus.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/softplus.py new file mode 100644 index 000000000..a5c9a2a8f --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/softplus.py @@ -0,0 +1,55 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.operators.softplus import Softplus + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(softplus_X)%%; + float *Y = %%LOAD_BUFFER(softplus_Y)%%; + + const int N = %%LOAD_BUFFER(softplus_N)%%; + const float beta = *((const float *)(& %%LOAD_BUFFER(softplus_beta)%%)); + const float beta_inv = 1.0 / beta; + + for (int gid = 0; gid < N; gid += 1) { + float val = X[gid]; + Y[gid] = logf(1.0F + expf(beta * val)) * beta_inv; + } +} +""" + + +# noinspection PyUnusedLocal +def softplus(op: Softplus, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "softplus_X": x, + "softplus_Y": y, + "softplus_N": y.variable.size, + "softplus_beta": op.parameters["beta"] + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/softsign.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/softsign.py new file mode 100644 index 000000000..269378730 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/softsign.py @@ -0,0 +1,52 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.operators.softsign import Softsign + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(softsign_X)%%; + float *Y = %%LOAD_BUFFER(softsign_Y)%%; + + const int N = %%LOAD_BUFFER(softsign_N)%%; + + for (int gid = 0; gid < N; gid += 1) { + float val = X[gid]; + Y[gid] = val / (fabsf(val) + 1.0F); + } +} +""" + + +# noinspection PyUnusedLocal +def softsign(op: Softsign, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "softsign_X": x, + "softsign_Y": y, + "softsign_N": y.variable.size + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/tanh.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/tanh.py index 83630985d..c0439b922 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/tanh.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/tanh.py @@ -1,23 +1,22 @@ from typing import List +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webassembly.kernel import Kernel -from webdnn.backend.webassembly.kernels import util -from webdnn.backend.webassembly.meta_buffer_injector import MetaBufferInjector -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.graph.operators.tanh import Tanh template = """ -void %%FUNC_NAME%%(const int * %%META_NAME%%) +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) { - const float *X = data_buffer + %%META_LOAD(relu_X_offset)%%; - float *Y = data_buffer + %%META_LOAD(relu_Y_offset)%%; + const float *X = %%LOAD_BUFFER(relu_X)%%; + float *Y = %%LOAD_BUFFER(relu_Y)%%; - const int N = %%META_LOAD(relu_N)%%; + const int N = %%LOAD_BUFFER(relu_N)%%; for (int gid = 0; gid < N; gid += 1) { float result = X[gid]; result = tanhf(result); - //Y[gid] = %%ELEMENTWISE_ATTACHABLE(result)%%; Y[gid] = result; } } @@ -25,32 +24,31 @@ # noinspection PyUnusedLocal -def tanh(op: Tanh, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, - metabuffer_injector: MetaBufferInjector = None) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] +def tanh(op: Tanh, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.order == y.variable.order assert x.variable.shape == y.variable.shape - if metabuffer_injector is None: - metabuffer_injector = MetaBufferInjector() - metabuffer_injector.register({ - "relu_X_offset": x.offset, - "relu_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "relu_X": x, + "relu_Y": y, "relu_N": y.variable.size }) - source = metabuffer_injector.inject(template) - func_name = util.add_canonical_suffix("tanh", source) - source = source.replace("%%FUNC_NAME%%", func_name) + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) kernel = Kernel( - {func_name: source}, - func_name, - metabuffer_injector.generate_buffer() + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/util.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/util.py deleted file mode 100644 index 221c6b21d..000000000 --- a/src/graph_transpiler/webdnn/backend/webassembly/kernels/util.py +++ /dev/null @@ -1,5 +0,0 @@ -import hashlib - - -def add_canonical_suffix(base_name: str, source: str): - return f"{base_name}_{hashlib.sha224(source.encode('utf-8')).hexdigest()}" diff --git a/src/graph_transpiler/webdnn/backend/webassembly/kernels/zero_padding_1d.py b/src/graph_transpiler/webdnn/backend/webassembly/kernels/zero_padding_1d.py new file mode 100644 index 000000000..4e168fdd7 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webassembly/kernels/zero_padding_1d.py @@ -0,0 +1,72 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webassembly.kernel import Kernel +from webdnn.graph.axis import Axis +from webdnn.graph.operators.zero_padding_1d import ZeroPadding1D +from webdnn.graph.order import OrderNTC + +template = """ +void %%FUNC_NAME%%(const int * %%META_BUFFER%%) +{ + const float *X = %%LOAD_BUFFER(zero_padding_1d_X)%%; + float *Y = %%LOAD_BUFFER(zero_padding_1d_Y)%%; + const int N = %%LOAD_BUFFER(zero_padding_1d_N)%%; + const int T1 = %%LOAD_BUFFER(zero_padding_1d_T1)%%; + const int C = %%LOAD_BUFFER(zero_padding_1d_C)%%; + const int T2 = %%LOAD_BUFFER(zero_padding_1d_T2)%%; + const int Pad1L = %%LOAD_BUFFER(zero_padding_1d_Pad1L)%%; + + for (int gid = 0; gid < N * T2 * C; gid += 1) { + const int c = gid % C; + const int t2 = gid / C % T2; + const int n = gid / C / T2; + + const int t1 = t2 - Pad1L; + float v = 0.0F; + if ((t1 >= 0) && (t1 < T1)) { + v = X[(n * T1 + t1) * C + c]; + } + + Y[gid] = v; + } +} +""" + + +# noinspection PyUnusedLocal +def zero_padding_1d(op: ZeroPadding1D, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.order == OrderNTC + assert y.variable.order == OrderNTC + + buffer_injector = BufferInjector() + buffer_injector.register({ + "zero_padding_1d_X": x, + "zero_padding_1d_Y": y, + "zero_padding_1d_N": x.variable.shape_dict[Axis.N], + "zero_padding_1d_T1": x.variable.shape_dict[Axis.T], + "zero_padding_1d_C": x.variable.shape_dict[Axis.C], + "zero_padding_1d_T2": y.variable.shape_dict[Axis.T], + "zero_padding_1d_Pad1L": op.parameters["padding"][0], + }) + # "zero_padding_1d_Pad1H": op.parameters["padding"][1] # unused in kernel + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/meta_buffer_injector.py b/src/graph_transpiler/webdnn/backend/webassembly/meta_buffer_injector.py deleted file mode 100644 index 214288216..000000000 --- a/src/graph_transpiler/webdnn/backend/webassembly/meta_buffer_injector.py +++ /dev/null @@ -1,76 +0,0 @@ -from typing import Dict, Union - -import numpy as np - -from webdnn.backend.webassembly.tag_parser import TagParser -from webdnn.util import flags - -MetaBufferContent = Union[int, float, bytes] - - -class MetaBufferInjector: - def __init__(self): - self.data = {} # type: Dict[str, MetaBufferContent] - self.offset_map = None # type: Dict[str, int] - self.buffer = None # type: bytes - self.arg_name = "meta_buffer" - - def register(self, data: Dict[str, any]): - self.data.update(data) - - def inject(self, source: str) -> str: - if self.offset_map is None: - self.generate_buffer() - - for tag in TagParser.parse(source): - if tag.name == "META_NAME": # メタバッファ名の取得 - source = source[:tag.span[0]] + self.arg_name + source[tag.span[1]:] - - elif tag.name == "META_LOAD": # データの読み込み - key = tag.args[0] - if key not in self.data: - raise KeyError(f"key '{key}' is not registered in MetaBufferInjector.") - - if flags.optimize.OPTIMIZE and flags.optimize.EMBED_METABUFFER_VALUE: - value = self.data[key] - if isinstance(value, int) or isinstance(value, float): - value = str(value) - - else: - value = f"{self.arg_name}[{self.offset_map[key]}]" - - else: - value = f"{self.arg_name}[{self.offset_map[key]}]" - - source = source[:tag.span[0]] + value + source[tag.span[1]:] - - return source - - def generate_buffer(self) -> bytes: - if self.buffer: - return self.buffer - - offset_map = {} - buffer = bytes() - for key, value in self.data.items(): - offset_map[key] = len(buffer) // 4 # sizeof(int) - - if isinstance(value, int): - buffer += np.array([value], dtype=np.int32).tobytes() - - elif isinstance(value, float): - buffer += np.array([value], dtype=np.float32).tobytes() - - elif isinstance(value, bytes): - if len(value) % 4 != 0: - value += bytes(4 - (len(value) % 4)) - - buffer += value - - else: - raise TypeError("MetaBufferInjector supports only int, float, and bytes contents.") - - self.offset_map = offset_map - self.buffer = buffer - - return self.buffer diff --git a/src/graph_transpiler/webdnn/backend/webassembly/operators/im2col.py b/src/graph_transpiler/webdnn/backend/webassembly/operators/im2col.py index 876eb6eeb..523a5a372 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/operators/im2col.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/operators/im2col.py @@ -11,16 +11,18 @@ class Im2Col(Operator): attributes = {PostAxiswise} - def __init__(self, name: Optional[str], ksize: IntOrTuple, stride: IntOrTuple, padding: IntOrTuple): + def __init__(self, name: Optional[str], ksize: IntOrTuple, stride: IntOrTuple, padding: IntOrTuple, + dilation_rate: IntOrTuple): super().__init__(name) self.parameters["ksize"] = to_tuple(ksize) self.parameters["stride"] = to_tuple(stride) self.parameters["padding"] = to_tuple(padding) + self.parameters["dilation_rate"] = to_tuple(dilation_rate) def __call__(self, im: Variable): N = im.shape_dict[Axis.N] - H2 = (im.shape_dict[Axis.H] + 2 * self.PH - self.KH) // self.SH + 1 - W2 = (im.shape_dict[Axis.W] + 2 * self.PW - self.KW) // self.SW + 1 + H2 = (im.shape_dict[Axis.H] + 2 * self.PH - self.WH) // self.SH + 1 + W2 = (im.shape_dict[Axis.W] + 2 * self.PW - self.WW) // self.SW + 1 C1 = im.shape_dict[Axis.C] col = Variable([N, H2, W2, self.KH * self.KW * C1], OrderNHWC) @@ -42,6 +44,10 @@ def stride(self) -> Tuple[int, int]: def padding(self) -> Tuple[int, int]: return self.parameters["padding"] + @property + def dilation_rate(self) -> Tuple[int, int]: + return self.parameters["dilation_rate"] + @property def KH(self) -> int: return self.parameters["ksize"][0] @@ -65,3 +71,29 @@ def PH(self) -> int: @property def PW(self) -> int: return self.parameters["padding"][1] + + @property + def DH(self) -> int: + return self.parameters["dilation_rate"][0] + + @property + def DW(self) -> int: + return self.parameters["dilation_rate"][1] + + @property + def WH(self) -> int: + """ + Input window height considering dilation. + Returns: + + """ + return self.DH * (self.KH - 1) + 1 + + @property + def WW(self) -> int: + """ + Input window width considering dilation. + Returns: + + """ + return self.DW * (self.KW - 1) + 1 diff --git a/src/graph_transpiler/webdnn/backend/webassembly/operators/sgemm.py b/src/graph_transpiler/webdnn/backend/webassembly/operators/sgemm.py index 29f28b7db..2fce2e30c 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/operators/sgemm.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/operators/sgemm.py @@ -1,20 +1,21 @@ -from typing import Optional, Iterable - -import numpy as np +from typing import Optional, Iterable, Union from webdnn.graph.operator import Operator from webdnn.graph.operators.attributes.have_weights import HaveWeights from webdnn.graph.operators.attributes.post_axiswise import PostAxiswise from webdnn.graph.order import Order +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable +from webdnn.util.misc import mul class Sgemm(Operator): attributes = {PostAxiswise, HaveWeights} - def __init__(self, name: Optional[str], M: int, N: int, K: int, out_shape: Iterable[int], out_order: Order, - transpose_A: bool, transpose_B: bool): + def __init__(self, name: Optional[str], M: Union[int, Placeholder], N: Union[int, Placeholder], + K: Union[int, Placeholder], + out_shape: Iterable[Union[int, Placeholder]], out_order: Order, transpose_A: bool, transpose_B: bool): super().__init__(name) # NOTE: out_shapeをIterableではなくCollectionにすればこれは解決する @@ -23,8 +24,8 @@ def __init__(self, name: Optional[str], M: int, N: int, K: int, out_shape: Itera # # noinspection PyTypeChecker assert len(out_shape) == out_order.ndim - - assert np.product(out_shape) == M * N + if Placeholder.check_resolved(out_shape) and Placeholder.check_resolved(M * N): + assert mul(out_shape) == M * N self.parameters["M"] = M self.parameters["N"] = N @@ -35,8 +36,10 @@ def __init__(self, name: Optional[str], M: int, N: int, K: int, out_shape: Itera self.parameters["transpose_B"] = transpose_B def __call__(self, A: Variable, B: Variable): - assert A.size == self.M * self.K - assert B.size == self.N * self.K + if Placeholder.check_resolved(A.size) and Placeholder.check_resolved(self.M * self.K): + assert A.size == self.M * self.K + if Placeholder.check_resolved(B.size) and Placeholder.check_resolved(self.N * self.K): + assert B.size == self.N * self.K self.append_input("A", A) self.append_input("B", B) @@ -50,21 +53,21 @@ def __call__(self, A: Variable, B: Variable): return C, @property - def M(self) -> int: - return int(self.parameters["M"]) + def M(self) -> Union[int, Placeholder]: + return self.parameters["M"] @property - def N(self) -> int: - return int(self.parameters["N"]) + def N(self) -> Union[int, Placeholder]: + return self.parameters["N"] @property - def K(self) -> int: - return int(self.parameters["K"]) + def K(self) -> Union[int, Placeholder]: + return self.parameters["K"] @property def transpose_A(self) -> bool: - return bool(self.parameters["transpose_A"]) + return self.parameters["transpose_A"] @property def transpose_B(self) -> bool: - return bool(self.parameters["transpose_B"]) + return self.parameters["transpose_B"] diff --git a/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/optimize_elementwise.py b/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/optimize_elementwise.py deleted file mode 100644 index b9c8951d0..000000000 --- a/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/optimize_elementwise.py +++ /dev/null @@ -1,50 +0,0 @@ -from typing import Tuple - -from webdnn.graph import traverse -from webdnn.graph.graph import Graph -from webdnn.graph.operators.attributes.elementwise import Elementwise -from webdnn.graph.operators.elu import Elu -from webdnn.graph.operators.relu import Relu -from webdnn.graph.operators.tanh import Tanh -from webdnn.graph.optimize_rule import OptimizeRule -from webdnn.graph.variable import Variable - - -# FIXME -class CombineElementwiseOperation(OptimizeRule): - def optimize(self, graph: Graph) -> Tuple[Graph, bool]: - return graph, False - # matches = traverse.search_sub_structure(graph, [PostElementwise, Variable, Elementwise]) - # flag_changed = False - # - # for match in matches: - # op1 = match[0] - # op2 = match[2] - # x = list(op2.inputs.values())[0] - # y = list(op2.outputs.values())[0] - # - # if isinstance(op2, Relu): - # op1.parameters["inline_elementwise"] = lambda exp: f"({exp}>0?{exp}:0)" - # - # elif isinstance(op2, Elu): - # op1.parameters["inline_elementwise"] = lambda exp: f"({exp}>0?{exp}:(exp({exp})-1))" - # - # elif isinstance(op2, Tanh): - # op1.parameters["inline_elementwise"] = lambda exp: f"(tanh({exp}))" - # - # else: - # continue - # - # op2.remove_all() - # op1.replace_output(x, y) - # - # flag_changed = True - # - # return graph, flag_changed - - -class OptimizeElementwise(OptimizeRule): - def __init__(self): - super(OptimizeElementwise, self).__init__() - - self.register(CombineElementwiseOperation()) diff --git a/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/sub_rules/replace_convolution_by_im2col.py b/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/sub_rules/replace_convolution_by_im2col.py index 75666ae78..4c5411200 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/sub_rules/replace_convolution_by_im2col.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/sub_rules/replace_convolution_by_im2col.py @@ -34,8 +34,8 @@ def optimize(self, graph: Graph) -> Tuple[Graph, bool]: w.change_order(OrderHWCN) assert old_y.order == OrderNHWC - if op.ksize[0] > 1 or op.ksize[1] > 1 or op.stride[0] > 1 or op.stride[1] > 1 or op.padding[0] > 0 or op.padding[1] > 0: - im2col = Im2Col(None, ksize=op.ksize, stride=op.stride, padding=op.padding) + if op.WH != 1 or op.WW != 1 or op.stride != (1, 1) or op.padding != (0, 0): + im2col = Im2Col(None, ksize=op.ksize, stride=op.stride, padding=op.padding, dilation_rate=op.dilation_rate) col, = im2col(x) col.change_order(OrderNHWC) diff --git a/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/webassembly_optimize_rule.py b/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/webassembly_optimize_rule.py index fc4f62357..2a5dccc8d 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/webassembly_optimize_rule.py +++ b/src/graph_transpiler/webdnn/backend/webassembly/optimize_rules/webassembly_optimize_rule.py @@ -1,6 +1,5 @@ from webdnn.backend.webassembly.optimize_rules.optimize_convolution2d import OptimizeConvolution2D from webdnn.backend.webassembly.optimize_rules.optimize_deconvolution2d import OptimizeDeconvolution2D -from webdnn.backend.webassembly.optimize_rules.optimize_elementwise import OptimizeElementwise from webdnn.backend.webassembly.optimize_rules.optimize_linear import OptimizeLinear from webdnn.backend.webassembly.optimize_rules.optimize_sgemm_eigen import OptimizeSgemmEigen from webdnn.graph.optimize_rule import OptimizeRule @@ -12,6 +11,5 @@ def __init__(self): self.register(OptimizeConvolution2D()) self.register(OptimizeDeconvolution2D()) - self.register(OptimizeElementwise()) self.register(OptimizeLinear()) self.register(OptimizeSgemmEigen()) diff --git a/src/graph_transpiler/webdnn/backend/webassembly/tag_parser.py b/src/graph_transpiler/webdnn/backend/webassembly/tag_parser.py deleted file mode 100644 index c3fc1c7e3..000000000 --- a/src/graph_transpiler/webdnn/backend/webassembly/tag_parser.py +++ /dev/null @@ -1,38 +0,0 @@ -import re -from typing import List, Tuple, NamedTuple - -from typing import Match - - -class Tag(NamedTuple): - original: str - name: str - args: List[str] - span: Tuple[int, int] - - -reg_tag = re.compile("%%([a-zA-Z0-9_]+)(?:\((.*)\))?%%", re.MULTILINE) - - -class TagParser: - @classmethod - def parse(cls, text: str) -> List[Tag]: - pos = 0 - result = [] - while True: - ma = reg_tag.search(text, pos) # type: Match - if ma is None: - break - pos = ma.end() - - original = ma.group(0) - name = ma.group(1) - args = [] if ma.group(2) is None else list(map(str.strip, ma.group(2).split(","))) - span = ma.span() - - # FIXME: This noinspection comment is not required. It's maybe a PyCharm's Bug? - # noinspection PyArgumentList - result.append(Tag(original, name, args, span)) - - result.sort(key=lambda x: x.span[0], reverse=True) - return result diff --git a/src/graph_transpiler/webdnn/backend/webassembly/webassembly_header.js b/src/graph_transpiler/webdnn/backend/webassembly/webassembly_header.js index 54f0ba04b..65c939bb8 100644 --- a/src/graph_transpiler/webdnn/backend/webassembly/webassembly_header.js +++ b/src/graph_transpiler/webdnn/backend/webassembly/webassembly_header.js @@ -5,18 +5,20 @@ onmessage = function (event) { switch (event.data.type) { case 'run': try { - var data_offset = Module._get_data_buffer(); + var data_offset = [Module._get_static_buffer(), Module._get_dynamic_buffer()]; for (var i = 0; i < event.data.inputs.length; i++) { var var_alloc = event.data.inputs[i]; - var data_buf = new Float32Array(Module.buffer, data_offset + var_alloc.offset * Float32Array.BYTES_PER_ELEMENT, var_alloc.size); + var data_buf = new Float32Array(Module.buffer, data_offset[var_alloc.space] + var_alloc.offset * Float32Array.BYTES_PER_ELEMENT, var_alloc.size); data_buf.set(var_alloc.data); } + Module._run(); + var outputs = []; var output_buffers = []; for (var i = 0; i < event.data.outputs.length; i++) { var var_alloc = event.data.outputs[i]; - var data_buf_view = new Float32Array(Module.buffer, data_offset + var_alloc.offset * Float32Array.BYTES_PER_ELEMENT, var_alloc.size); + var data_buf_view = new Float32Array(Module.buffer, data_offset[var_alloc.space] + var_alloc.offset * Float32Array.BYTES_PER_ELEMENT, var_alloc.size); var data_buf_copy = new Float32Array(data_buf_view.length); data_buf_copy.set(data_buf_view); outputs.push(data_buf_copy); @@ -24,21 +26,41 @@ onmessage = function (event) { } postMessage(outputs, output_buffers); } catch (ex) { - postMessage({ 'error': ex }); + postMessage({ 'error': ex.message }); } break; + case 'weight': try { - var weight_offset = Module._get_weight_buffer(); - var weight_buf = new Float32Array(Module.buffer, weight_offset, event.data.data.length); + var weight_buf = new Float32Array(Module.buffer, Module._get_static_buffer(), event.data.data.length); weight_buf.set(event.data.data); postMessage(0); } catch (ex) { - postMessage({ 'error': ex }); + postMessage({ 'error': ex.message }); } break; + + case 'set_dynamic_buffer': + try { + // event.data = {size: number_of_elements, data = [kernel_order_0, offset0, value0, kernel_order_1, ...]} + var dynamic_ptr = Module._allocate_dynamic_buffer(event.data.size); + if (dynamic_ptr === 0) { + throw Error('Dynamic buffer cannot be allocated'); + } + var data_to_set = event.data.data; + var data_idx = 0; + while (data_idx < data_to_set.length) { + Module._set_placeholder_value(data_to_set[data_idx], data_to_set[data_idx + 1], data_to_set[data_idx + 2]); + data_idx += 3; + } + postMessage(0); + } catch (ex) { + postMessage({ 'error': ex.message }); + } + break; + default: - postMessage({ 'error': new Error('Unknown message') }); + postMessage({ 'error': 'Unknown message' }); break; } }; @@ -49,4 +71,4 @@ Module.quit = function (status, toThrow) { Module.onRuntimeInitialized = function () { postMessage(0); -} +}; diff --git a/src/graph_transpiler/webdnn/backend/webgpu/allocator.py b/src/graph_transpiler/webdnn/backend/webgpu/allocator.py deleted file mode 100644 index a6c010c28..000000000 --- a/src/graph_transpiler/webdnn/backend/webgpu/allocator.py +++ /dev/null @@ -1,386 +0,0 @@ -import math -from typing import Dict, Tuple, List, Set - -import numpy as np - -from webdnn.backend.interface.memory_layout import IMemoryLayout, IAllocation -from webdnn.graph import traverse -from webdnn.graph.graph import Graph -from webdnn.graph.operator import Operator -from webdnn.graph.operators.attributes.inplace import Inplace -from webdnn.graph.variable import Variable -from webdnn.graph.variables.attributes.constant import Constant -from webdnn.graph.variables.constant_variable import ConstantVariable -from webdnn.util import json, flags - - -class AllocationAnalysisData: - variable: Variable - start: int - end: int - offset: int - size: int - - def __init__(self, variable, start, end=-1, offset=-1): - self.variable = variable - self.size = variable.size - self.start = start - self.end = end - self.offset = offset - - -class Allocation(json.SerializableMixin, IAllocation): - variable: Variable - offset: int - - def __init__(self, - variable: Variable, - offset: int): - self.variable = variable - self.offset = offset - - @property - def is_constant(self) -> bool: - return isinstance(self.variable, ConstantVariable) - - @property - def size(self) -> int: - return self.variable.size - - def _to_serializable_(self): - return { - "name": self.variable.name, - "offset": self.offset, - "size": self.size - } - - -class MemoryLayout(json.SerializableMixin, IMemoryLayout): - __dict__: Dict[str, Allocation] - - def __init__(self): - self.__dict__ = {} - - def _to_serializable_(self): - return { - "total_size": self.size, - "allocation": {a.variable.name: a for _, a in self.__dict__.items()} - } - - def __len__(self): - return len(self.__dict__) - - def __getitem__(self, var: Variable): - return self.__dict__[var.name] - - def __contains__(self, var: Variable): - return var.name in self.__dict__ - - def append(self, var: Variable, offset: int = -1): - if offset == -1: - offset = self.size - - self.__dict__[var.name] = Allocation(var, offset) - - @property - def size(self) -> int: - size = 0 - for _, a in self.__dict__.items(): - size = max(a.offset + a.size, size) - - return size - - -class Allocator: - layout: MemoryLayout - - @classmethod - def allocate(cls, graph: Graph) -> Tuple[MemoryLayout, MemoryLayout, np.array]: - variables = set(traverse.listup_variables(graph)) - constants = set(traverse.filter_nodes(variables, Constant)) # type: Set[ConstantVariable] - variables = variables.difference(constants) - - variables = list(variables) - constants = list(constants) - - constants_layout, data = cls.allocate_constants(constants) - variables_layout = cls.allocate_variables(graph, variables) - return variables_layout, constants_layout, data - - @classmethod - def allocate_constants(cls, constants: List[ConstantVariable]) -> Tuple[MemoryLayout, np.ndarray]: - layout = MemoryLayout() - - for constant in constants: - if constant in layout: - continue - - layout.append(constant) - - buffer = np.zeros(layout.size, dtype=np.float32) - for constant in constants: - allocation = layout[constant] - buffer[allocation.offset:allocation.offset + allocation.size] = constant.data.flatten() - - return layout, buffer - - @classmethod - def allocate_variables(cls, graph: Graph, variables: List[Variable]) -> MemoryLayout: - ops = traverse.listup_operators(graph) - layout = MemoryLayout() - - if flags.optimize.OPTIMIZE and flags.optimize.OPTIMIZE_MEMORY_ALLOCATION: - analysis_list = _analyse_variable_lifetime(graph, ops, variables) - - _optimize_allocation_offset(analysis_list) - - allocation_dict = {item.variable: item.offset for item in analysis_list} - for var in variables: - original_var = var - while "inplace_src" in var.parameters: - var = var.parameters["inplace_src"] - - layout.append(original_var, allocation_dict[var]) - - else: - for variable in variables: - layout.append(variable) - - if flags.VISUALIZE_MEMORY_ALLOCATION: - _visualize_allocation(layout, graph, variables, ops) - - return layout - - -def _analyse_variable_lifetime(graph: Graph, ops: List[Operator], variables: List[Variable]): - # 計算グラフを辿りながら、retain回数をカウントし、生存期間のテーブルを作成する - - LIFETIME_FOREVER = len(ops) + 1 - analysis_table: Dict[Variable, AllocationAnalysisData] = {} - - retain_count: Dict[Variable, int] = {v: 0 for v in variables} - allocated: Set[Variable] = set() - - for var in graph.inputs: - if isinstance(var, ConstantVariable): - continue - - analysis_table[var] = AllocationAnalysisData(var, 0, LIFETIME_FOREVER) - - for t, op in enumerate(ops): - for var in op.outputs.values(): - if isinstance(var, ConstantVariable): - continue - - if var not in allocated: - # 新規に確保する - flag_allocated = False - - if flags.optimize.OPTIMIZE and flags.optimize.OPTIMIZE_INPLACE_OPERATION \ - and not flag_allocated \ - and traverse.check_attribute_match(op, Inplace): - - # Inplace処理 - - inplace = op.get_attribute(Inplace)[0] # type: Inplace - # 入力のメモリをそのまま使う - v_in = inplace.get_input() - while "inplace_src" in v_in.parameters: - v_in = v_in.parameters["inplace_src"] - var.parameters["inplace_src"] = v_in - retain_count[v_in] += len(var.input_to) - - allocated.add(var) - flag_allocated = True - - if not flag_allocated: - # 新しくメモリを確保 - analysis_table[var] = AllocationAnalysisData(var, t, LIFETIME_FOREVER) - retain_count[var] = len(var.input_to) - - allocated.add(var) - flag_allocated = True - - if not flag_allocated: - raise ValueError("[WebGPUAllocator] Memory Allocation Failed.") - - for var in op.inputs.values(): - if isinstance(var, ConstantVariable): - continue - - while "inplace_src" in var.parameters: - var = var.parameters["inplace_src"] - retain_count[var] -= 1 - - if retain_count[var] == 0: - # 解放はこの層が終わってから! - analysis_table[var].end = t + 1 - - return [x for x in analysis_table.values()] - - -def _optimize_allocation_offset(analysis_list: List[AllocationAnalysisData]): - analysis_list = sorted(analysis_list, key=lambda x: x.variable.size, reverse=True) - analysis_list = sorted(analysis_list, key=lambda x: x.end) - analysis_list = sorted(analysis_list, key=lambda x: x.end - x.start, reverse=True) - analysis_list = list(analysis_list) - memory_offset_table = {} - queue = list(analysis_list) - - while len(queue) > 0: - for item1 in queue: - offset = 0 - - flag_retry = True - while flag_retry: - flag_retry = False - - for t in range(item1.start, item1.end): - if t not in memory_offset_table: - continue - - for item2 in memory_offset_table[t]: - if item2.offset + item2.size <= offset or offset + item1.size <= item2.offset: - continue - - else: - offset = item2.offset + item2.size - flag_retry = True - break - - if flag_retry: - break - - # align for 16byte - item1.offset = math.ceil(offset / 4) * 4 - - queue = list(sorted(queue, key=lambda x: x.offset)) - item1 = queue.pop(0) - for t in range(item1.start, item1.end): - if t not in memory_offset_table: - memory_offset_table[t] = [] - - memory_offset_table[t].append(item1) - - -def _visualize_allocation(layout: MemoryLayout, graph: Graph, variables: List[Variable], ops: List[Operator]): - UNIT_HEIGHT = 14 - analysis_list = _analyse_variable_lifetime(graph, ops, variables) - total_size = layout.size - - class RenderingInfo: - names: List[str] - data: AllocationAnalysisData - - def __init__(self, data: AllocationAnalysisData): - self.names = [] - self.data = data - - @property - def offset(self): - return layout[self.data.variable].offset - - @property - def size(self): - return layout[self.data.variable].size - - @property - def top(self): - return f"{self.data.start * UNIT_HEIGHT}px" - - @property - def height(self): - return f"{(self.data.end - self.data.start) * UNIT_HEIGHT + 1}px" - - @property - def left(self): - return f"{self.offset * 100 / total_size}%" - - @property - def width(self): - return f"calc({self.size * 100 / total_size}% + 1px)" - - def generate_html(self): - return f"""
-

{", ".join(self.names)}

-
""" - - allocation_dict = {item.variable: item for item in analysis_list} - rendering_dict: Dict[Variable, RenderingInfo] = {} - - html = """ - - - - -
-

Memory Allocation Visualization

-
-

Total allocation size: """ + str(layout.size * 4) + """[byte]

-

# of allocated variables: """ + str(len(layout)) + """

-
-
-

縦軸:時間経過(上から下へ)

-

横軸:メモリアドレス

-

各要素はカーソルホバーで詳細が見られます。

-
-
-
-""" - - for variable in variables: - original_var = variable - while "inplace_src" in variable.parameters: - variable = variable.parameters["inplace_src"] - - if variable not in rendering_dict: - rendering_dict[variable] = RenderingInfo(allocation_dict[variable]) - - rendering_dict[variable].names.append(original_var.name) - - for item in rendering_dict.values(): - html += item.generate_html() - - html += """ -
- - -""" - - with open('memory_visualize.html', "w+") as f: - f.write(html) diff --git a/src/graph_transpiler/webdnn/backend/webgpu/attributes/inline_inject.py b/src/graph_transpiler/webdnn/backend/webgpu/attributes/inline_inject.py index 449c3d698..4b0383825 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/attributes/inline_inject.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/attributes/inline_inject.py @@ -1,7 +1,7 @@ from typing import Callable +from webdnn.graph import node from webdnn.graph.attribute import Attribute -from webdnn.graph.interface import INode from webdnn.graph.operators.attributes.inplace import Inplace @@ -9,8 +9,8 @@ class InlineInplace(Attribute): injector: Callable[[str], str] inplace: Inplace - def __init__(self, node: INode, injector: Callable[[str], str], inplace: Inplace): - super(InlineInplace, self).__init__(node) + def __init__(self, base: "node.Node", injector: Callable[[str], str], inplace: Inplace): + super(InlineInplace, self).__init__(base) self.injector = injector self.inplace = inplace diff --git a/src/graph_transpiler/webdnn/backend/webgpu/attributes/lstm_optimized.py b/src/graph_transpiler/webdnn/backend/webgpu/attributes/lstm_optimized.py new file mode 100644 index 000000000..058b26110 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/attributes/lstm_optimized.py @@ -0,0 +1,18 @@ +from webdnn.graph.attribute import Attribute +from webdnn.graph.axis import Axis +from webdnn.graph.operators.lstm import LSTM + + +class LSTMOptimized(Attribute): + def __init__(self, base: LSTM): + super(LSTMOptimized, self).__init__(base) + if "w_input" not in base.inputs: + raise KeyError("[LSTMOptimized] 'w_input' is not found in inputs of LSTM operator." + "LSTMOptimized attribute must be attached before 'w_input' is removed") + + if "w_hidden" not in base.inputs: + raise KeyError("[LSTMOptimized] 'w_hidden' is not found in inputs of LSTM operator." + "LSTMOptimized attribute must be attached before 'w_hidden' is removed") + + self.C1 = base.inputs["w_input"].shape_dict[Axis.C] + self.C2 = base.inputs["w_hidden"].shape_dict[Axis.C] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/generator.py b/src/graph_transpiler/webdnn/backend/webgpu/generator.py index 20640f5b7..b093414f5 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/generator.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/generator.py @@ -8,27 +8,39 @@ import os.path as path import subprocess import tempfile as tmp -from typing import List +from webdnn.backend.code_generator.allocator import Allocator +from webdnn.backend.interface.generator import DescriptorGenerator from webdnn.backend.interface.graph_descriptor import IGraphExecutionData -from webdnn.backend.webgpu.allocator import Allocator, MemoryLayout from webdnn.backend.webgpu.graph_descriptor import GraphDescriptor from webdnn.backend.webgpu.kernel import Kernel from webdnn.backend.webgpu.kernels.average_pooling_2d import average_pooling_2d from webdnn.backend.webgpu.kernels.axiswise_bias import axiswise_bias from webdnn.backend.webgpu.kernels.axiswise_scale import axiswise_scale +from webdnn.backend.webgpu.kernels.clipped_relu import clipped_relu from webdnn.backend.webgpu.kernels.col2im import col2im from webdnn.backend.webgpu.kernels.concat import concat from webdnn.backend.webgpu.kernels.elementwise_sum import elementwise_sum from webdnn.backend.webgpu.kernels.elu import elu +from webdnn.backend.webgpu.kernels.embedding import embedding from webdnn.backend.webgpu.kernels.flatten import flatten +from webdnn.backend.webgpu.kernels.hard_sigmoid import hard_sigmoid from webdnn.backend.webgpu.kernels.im2col import im2col +from webdnn.backend.webgpu.kernels.leaky_relu import leaky_relu from webdnn.backend.webgpu.kernels.local_response_normalization import local_response_normalization +from webdnn.backend.webgpu.kernels.lstm import lstm from webdnn.backend.webgpu.kernels.max_pooling_2d import max_pooling_2d +from webdnn.backend.webgpu.kernels.reinterpret_axis import reinterpret_axis from webdnn.backend.webgpu.kernels.relu import relu +from webdnn.backend.webgpu.kernels.reshape import reshape from webdnn.backend.webgpu.kernels.scalar_affine import scalar_affine from webdnn.backend.webgpu.kernels.sgemm import sgemm +from webdnn.backend.webgpu.kernels.sigmoid import sigmoid +from webdnn.backend.webgpu.kernels.softmax import softmax +from webdnn.backend.webgpu.kernels.softplus import softplus +from webdnn.backend.webgpu.kernels.softsign import softsign from webdnn.backend.webgpu.kernels.tanh import tanh +from webdnn.backend.webgpu.kernels.zero_padding_1d import zero_padding_1d from webdnn.backend.webgpu.operators.col2im import Col2Im from webdnn.backend.webgpu.operators.im2col import Im2Col from webdnn.backend.webgpu.operators.sgemm import Sgemm @@ -39,20 +51,32 @@ from webdnn.graph.operators.average_pooling_2d import AveragePooling2D from webdnn.graph.operators.axiswise_bias import AxiswiseBias from webdnn.graph.operators.axiswise_scale import AxiswiseScale +from webdnn.graph.operators.clipped_relu import ClippedRelu from webdnn.graph.operators.concat import Concat from webdnn.graph.operators.elementwise_sum import ElementwiseSum from webdnn.graph.operators.elu import Elu +from webdnn.graph.operators.embedding import Embedding from webdnn.graph.operators.flatten import Flatten +from webdnn.graph.operators.hard_sigmoid import HardSigmoid +from webdnn.graph.operators.leaky_relu import LeakyRelu from webdnn.graph.operators.local_response_normalization import LocalResponseNormalization +from webdnn.graph.operators.lstm import LSTM from webdnn.graph.operators.max_pooling_2d import MaxPooling2D +from webdnn.graph.operators.reinterpret_axis import ReinterpretAxis from webdnn.graph.operators.relu import Relu +from webdnn.graph.operators.reshape import Reshape from webdnn.graph.operators.scalar_affine import ScalarAffine +from webdnn.graph.operators.sigmoid import Sigmoid +from webdnn.graph.operators.softmax import Softmax +from webdnn.graph.operators.softplus import Softplus +from webdnn.graph.operators.softsign import Softsign from webdnn.graph.operators.tanh import Tanh -from webdnn.util import flags +from webdnn.graph.operators.zero_padding_1d import ZeroPadding1D +from webdnn.util import flags, console from webdnn.util.json import json -class GraphExecutionData(IGraphExecutionData): +class GraphExecutionData(IGraphExecutionData[Kernel]): descriptor: GraphDescriptor def __init__(self, descriptor: GraphDescriptor, constants: bytes): @@ -85,97 +109,93 @@ def validate_kernel_source(descriptor: GraphDescriptor): with open(source_path, "w+") as f: f.write(source) - result = subprocess.run(["xcrun", "-sdk", "macosx", "metal", source_path, "-o", lib_path]) - if result.returncode != 0: - print("Generated kernel source is invalid.") - exit(result.returncode) - - -def generate(graph: Graph, constant_encoder_name: str = None) -> GraphExecutionData: - graph, _ = WebGPUOptimizeRule().optimize(graph) - if flags.DEBUG: - traverse.dump(graph) - - variables_layout, constants_layout, constants_data = Allocator.allocate(graph) - - constant_encoder = ConstantEncoder.get_encoder(constant_encoder_name) - constants_bytes = constant_encoder.encode(constants_layout, constants_data) - - if flags.DEBUG: - print(f"[GraphDescriptorGeneratorWebGPU] allocated constant-buffer size: {constants_layout.size * 4} [Byte]") - print(f"[GraphDescriptorGeneratorWebGPU] encoded constant-buffer size: {len(constants_bytes)} [Byte]") - print(f"[GraphDescriptorGeneratorWebGPU] allocated variable-buffer size: {variables_layout.size * 4} [Byte]") - - kernels = generate_kernels(graph, constants_layout, variables_layout) - - descriptor = GraphDescriptor( - kernels=kernels, - constants_layout=constants_layout, - variables_layout=variables_layout, - inputs=graph.inputs, - outputs=graph.outputs, - constants_encoding=constant_encoder.name, - licenses=graph.licenses) - - if flags.optimize.VALIDATE_GENERATED_SOURCE: + with open(os.devnull, "w") as f: + result = subprocess.run(["type", "xcrun"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode != 0: + console.warning( + "[WebGPUDescriptorGenerator] 'xcrun' command is not found. validation of generated source code in webgpu backend is " + "skipped.") + return + + with open(os.devnull, "w") as f: + result = subprocess.run(["xcrun", "-sdk", "macosx", "metal", source_path, "-o", lib_path], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode == 0: + if result.stderr == b"": + console.debug("[WebGPUDescriptorGenerator] Generated kernel source is valid.") + + else: + console.warning("[WebGPUDescriptorGenerator] In validating kernel source, warnings are generated.") + console.stderr(result.stderr.decode("utf-8")) + + else: + console.error("[WebGPUDescriptorGenerator] Generated kernel source is invalid.") + console.stderr(result.stderr.decode("utf-8")) + exit(result.returncode) + + +class WebGPUDescriptorGenerator(DescriptorGenerator[Kernel, GraphExecutionData]): + @classmethod + def generate(cls, graph: Graph, **kwargs): + graph, _ = WebGPUOptimizeRule().optimize(graph) if flags.DEBUG: - print("[GraphDescriptorGeneratorWebGPU] validate generated kernel source") - - validate_kernel_source(descriptor) - - return GraphExecutionData(descriptor, constants_bytes) - - -def generate_kernels(graph: Graph, constants_layout: MemoryLayout, variables_layout: MemoryLayout) -> List[Kernel]: - kernels: List[Kernel] = [] - - for op in traverse.listup_operators(graph): - if isinstance(op, AxiswiseBias): - kernels += axiswise_bias(op, constants_layout, variables_layout) - - elif isinstance(op, Relu): - kernels += relu(op, constants_layout, variables_layout) - - elif isinstance(op, Elu): - kernels += elu(op, constants_layout, variables_layout) - - elif isinstance(op, Tanh): - kernels += tanh(op, constants_layout, variables_layout) - - elif isinstance(op, LocalResponseNormalization): - kernels += local_response_normalization(op, constants_layout, variables_layout) - - elif isinstance(op, MaxPooling2D): - kernels += max_pooling_2d(op, constants_layout, variables_layout) - - elif isinstance(op, AveragePooling2D): - kernels += average_pooling_2d(op, constants_layout, variables_layout) - - elif isinstance(op, AxiswiseScale): - kernels += axiswise_scale(op, constants_layout, variables_layout) - - elif isinstance(op, ElementwiseSum): - kernels += elementwise_sum(op, constants_layout, variables_layout) - - elif isinstance(op, Flatten): - kernels += flatten(op, constants_layout, variables_layout) - - elif isinstance(op, Sgemm): - kernels += sgemm(op, constants_layout, variables_layout) - - elif isinstance(op, Im2Col): - kernels += im2col(op, constants_layout, variables_layout) - - elif isinstance(op, Col2Im): - kernels += col2im(op, constants_layout, variables_layout) - - elif isinstance(op, ScalarAffine): - kernels += scalar_affine(op, constants_layout, variables_layout) - - elif isinstance(op, Concat): - kernels += concat(op, constants_layout, variables_layout) - - else: - raise NotImplementedError(f"{op} is Unknown for WebGPUDescriptorGenerator") - - return kernels + traverse.dump(graph) + + memory_layout = Allocator.allocate(graph) + console.debug(f"[GraphDescriptorGeneratorWebGPU] memory_layout total size: {memory_layout.total_size * 4}") + console.debug(f"[GraphDescriptorGeneratorWebGPU] memory_layout static size: {memory_layout.static_size * 4}") + console.debug(f"[GraphDescriptorGeneratorWebGPU] memory_layout dynamic size: {memory_layout.dynamic_size * 4}") + + constant_encoder = ConstantEncoder.get_encoder(kwargs.get("constant_encoder_name", None)) + constants_bytes = constant_encoder.encode(memory_layout) + + console.debug(f"[GraphDescriptorGeneratorWebGPU] constants encoded size: {len(constants_bytes)}") + + kernels = cls.generate_kernels(graph, memory_layout) + + descriptor = GraphDescriptor( + kernels=kernels, + memory_layout=memory_layout, + inputs=graph.inputs, + outputs=graph.outputs, + constants_encoding=constant_encoder.name, + licenses=graph.licenses + ) + + if flags.optimize.VALIDATE_GENERATED_SOURCE: + validate_kernel_source(descriptor) + + return GraphExecutionData(descriptor, constants_bytes) + + +def generate(graph: Graph, **kwargs): + return WebGPUDescriptorGenerator.generate(graph, **kwargs) + + +WebGPUDescriptorGenerator.register_handler(AveragePooling2D)(average_pooling_2d) +WebGPUDescriptorGenerator.register_handler(AxiswiseBias)(axiswise_bias) +WebGPUDescriptorGenerator.register_handler(AxiswiseScale)(axiswise_scale) +WebGPUDescriptorGenerator.register_handler(ClippedRelu)(clipped_relu) +WebGPUDescriptorGenerator.register_handler(Col2Im)(col2im) +WebGPUDescriptorGenerator.register_handler(Concat)(concat) +WebGPUDescriptorGenerator.register_handler(ElementwiseSum)(elementwise_sum) +WebGPUDescriptorGenerator.register_handler(Elu)(elu) +WebGPUDescriptorGenerator.register_handler(Embedding)(embedding) +WebGPUDescriptorGenerator.register_handler(Flatten)(flatten) +WebGPUDescriptorGenerator.register_handler(HardSigmoid)(hard_sigmoid) +WebGPUDescriptorGenerator.register_handler(Im2Col)(im2col) +WebGPUDescriptorGenerator.register_handler(LeakyRelu)(leaky_relu) +WebGPUDescriptorGenerator.register_handler(LocalResponseNormalization)(local_response_normalization) +WebGPUDescriptorGenerator.register_handler(LSTM)(lstm) +WebGPUDescriptorGenerator.register_handler(MaxPooling2D)(max_pooling_2d) +WebGPUDescriptorGenerator.register_handler(ReinterpretAxis)(reinterpret_axis) +WebGPUDescriptorGenerator.register_handler(Relu)(relu) +WebGPUDescriptorGenerator.register_handler(Reshape)(reshape) +WebGPUDescriptorGenerator.register_handler(ScalarAffine)(scalar_affine) +WebGPUDescriptorGenerator.register_handler(Sgemm)(sgemm) +WebGPUDescriptorGenerator.register_handler(Sigmoid)(sigmoid) +WebGPUDescriptorGenerator.register_handler(Softmax)(softmax) +WebGPUDescriptorGenerator.register_handler(Softplus)(softplus) +WebGPUDescriptorGenerator.register_handler(Softsign)(softsign) +WebGPUDescriptorGenerator.register_handler(Tanh)(tanh) +WebGPUDescriptorGenerator.register_handler(ZeroPadding1D)(zero_padding_1d) diff --git a/src/graph_transpiler/webdnn/backend/webgpu/graph_descriptor.py b/src/graph_transpiler/webdnn/backend/webgpu/graph_descriptor.py index 7fbabf6e1..66ed6258f 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/graph_descriptor.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/graph_descriptor.py @@ -1,10 +1,11 @@ from collections import OrderedDict -from typing import Iterable, Dict +from typing import Iterable, Dict, List, Set, Tuple +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.backend.interface.graph_descriptor import IGraphDescriptor -from webdnn.backend.webgpu.allocator import MemoryLayout from webdnn.backend.webgpu.kernel import Kernel from webdnn.graph import traverse +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable from webdnn.graph.variables.attributes.constant import Constant from webdnn.util import json, flags @@ -14,14 +15,12 @@ using namespace metal; #define OPTIMIZE {"1" if flags.optimize.OPTIMIZE else "0"} - """ class GraphDescriptor(json.SerializableMixin, IGraphDescriptor): kernels: Iterable[Kernel] - constants_layout: MemoryLayout - variables_layout: MemoryLayout + memory_layout: MemoryLayout inputs: Iterable[Variable] outputs: Iterable[Variable] constants_encoding: str @@ -29,15 +28,13 @@ class GraphDescriptor(json.SerializableMixin, IGraphDescriptor): def __init__(self, kernels: Iterable[Kernel], - constants_layout: MemoryLayout, - variables_layout: MemoryLayout, + memory_layout: MemoryLayout, inputs: Iterable[Variable], outputs: Iterable[Variable], constants_encoding: str, licenses: Dict[str, str]): self.kernels = kernels - self.constants_layout = constants_layout - self.variables_layout = variables_layout + self.memory_layout = memory_layout self.inputs = inputs self.outputs = outputs self.constants_encoding = constants_encoding @@ -45,7 +42,10 @@ def __init__(self, def concat_kernel_sources(self): func_sources = OrderedDict() - prototype_sources = OrderedDict() + + # with open(path.join(path.dirname(__file__), "./libs.metal")) as f: + # libs = f.read() + libs = "" for kernel in self.kernels: for func_name, source in kernel.func_sources.items(): @@ -54,26 +54,40 @@ def concat_kernel_sources(self): else: func_sources[func_name] = source - for func_name, source in kernel.prototype_sources.items(): - if func_name in prototype_sources: - assert prototype_sources[func_name] == source - else: - prototype_sources[func_name] = source - combined_source = \ source_header + \ - "\n".join(prototype_sources.values()) + \ + libs + \ "\n".join(func_sources.values()) return combined_source + def get_all_placeholders(self): + unresolved_variables = [] # type: List[Tuple[int, Placeholder]] + placeholders_set = set() # type: Set[Placeholder] + + for kernel in self.kernels: + unresolved_variables += kernel.exec_info.unresolved_value_list + + for offset, v in unresolved_variables: + placeholders_set.update(v.get_depend_placeholders()) + + for kernel in self.kernels: + placeholders_set.update(kernel.exec_info.threadgroups_per_grid.unresolved_placeholders) + placeholders_set.update(kernel.exec_info.threads_per_thread_group.unresolved_placeholders) + + placeholders = {p.label: None for p in placeholders_set} + + return placeholders + def _to_serializable_(self): + placeholders = self.get_all_placeholders() + return { "kernel_source": self.concat_kernel_sources(), "exec_infos": [kernel.exec_info for kernel in self.kernels], - "weight_allocation": self.constants_layout, "weight_encoding": self.constants_encoding, - "variable_allocation": self.variables_layout, + "memory_layout": self.memory_layout, + "placeholders": placeholders, "inputs": [v.parameters["name"] for v in self.inputs if not traverse.check_attribute_match(v, Constant)], "outputs": [v.parameters["name"] for v in self.outputs], "licenses": self.licenses diff --git a/src/graph_transpiler/webdnn/backend/webgpu/injectors/meta_injector.py b/src/graph_transpiler/webdnn/backend/webgpu/injectors/meta_injector.py deleted file mode 100644 index da0a69d5f..000000000 --- a/src/graph_transpiler/webdnn/backend/webgpu/injectors/meta_injector.py +++ /dev/null @@ -1,74 +0,0 @@ -from typing import Dict, Union - -import numpy as np - -from webdnn.backend.webgpu.injector import Tag, Injector -from webdnn.util import flags - -MetaBufferContent = Union[int, float, bytes] - - -class MetaInjector(Injector): - def __init__(self): - self.data = {} # type: Dict[str, MetaBufferContent] - self.offset_map = None # type: Dict[str, int] - self.buffer = None # type: bytes - self.arg_name = "meta_buffer" - - def register(self, data: Dict[str, any]): - self.data.update(data) - - def inject_tag(self, tag: Tag): - if self.offset_map is None: - self._generate_buffer() - - if tag.name == "META_NAME": # メタバッファ名の取得 - return self.arg_name - - elif tag.name == "META_LOAD": # データの読み込み - key = tag.args[0] - if key not in self.data: - raise KeyError(f"key '{key}' is not registered in MetaBufferInjector.") - - if flags.optimize.OPTIMIZE and flags.optimize.EMBED_METABUFFER_VALUE: - if isinstance(self.data[key], int) or isinstance(self.data[key], float): - return str(self.data[key]) - - else: - return f"{self.arg_name}[{self.offset_map[key]}]" - - else: - return f"{self.arg_name}[{self.offset_map[key]}]" - - else: - return tag.original - - def _generate_buffer(self) -> bytes: - if self.buffer: - return self.buffer - - offset_map = {} - buffer = bytes() - for key, value in self.data.items(): - offset_map[key] = len(buffer) // 4 # sizeof(int) - - if isinstance(value, int): - buffer += np.array([value], dtype=np.int32).tobytes() - - elif isinstance(value, float): - buffer += np.array([value], dtype=np.float32).tobytes() - - elif isinstance(value, bytes): - if len(value) % 4 != 0: - value += bytes(4 - (len(value) % 4)) - - buffer += value - - else: - raise TypeError("MetaBufferInjector supports only int, float, and bytes contents. " - + f"\"{key} is {type(value)}\".") - - self.offset_map = offset_map - self.buffer = buffer - - return self.buffer diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernel.py b/src/graph_transpiler/webdnn/backend/webgpu/kernel.py index 9747c01cf..d96fcab79 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernel.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernel.py @@ -1,7 +1,8 @@ -from typing import Dict +from typing import Dict, Tuple, List import numpy as np +from webdnn.graph.placeholder import Placeholder from webdnn.util import json @@ -18,35 +19,53 @@ def __init__(self, width: int = 1, height: int = 1, depth: int = 1): def _to_serializable_(self): return {"width": self.width, "height": self.height, "depth": self.depth} + @property + def unresolved_placeholders(self): + result = [] + + if not Placeholder.check_resolved(self.width): + result += [self.width] + + if not Placeholder.check_resolved(self.height): + result += [self.height] + + if not Placeholder.check_resolved(self.depth): + result += [self.depth] + + return result + class KernelExecutionInfo(json.SerializableMixin): entry_func_name: str threadgroups_per_grid: GPUSize threads_per_thread_group: GPUSize meta_buffer: bytes + unresolved_value_list: List[Tuple[int, Placeholder]] def __init__(self, entry_func_name: str, threadgroups_per_grid: GPUSize, threads_per_thread_group: GPUSize, - meta_buffer: bytes): + meta_buffer: bytes, + unresolved_value_list: List[Tuple[int, Placeholder]] = None): self.entry_func_name = entry_func_name self.threadgroups_per_grid = threadgroups_per_grid self.threads_per_thread_group = threads_per_thread_group self.meta_buffer = meta_buffer + self.unresolved_value_list = [] if unresolved_value_list is None else unresolved_value_list # type:List[Tuple[int, Placeholder]] def _to_serializable_(self): return { "entry_func_name": self.entry_func_name, "threadgroups_per_grid": self.threadgroups_per_grid, "threads_per_thread_group": self.threads_per_thread_group, - "meta_buffer": np.frombuffer(self.meta_buffer, dtype=np.uint8).tolist() + "meta_buffer": np.frombuffer(self.meta_buffer, dtype=np.uint8).tolist(), + "unresolved_value_list": [{"offset": v[0], "placeholder": v[1]} for v in self.unresolved_value_list] } class Kernel: func_sources: Dict[str, str] - prototype_sources: Dict[str, str] exec_info: KernelExecutionInfo def __init__(self, @@ -55,12 +74,12 @@ def __init__(self, threadgroups_per_grid, threads_per_thread_group, meta_buffer: bytes, - prototype_sources: Dict[str, str] = None): + unresolved_value_list: List[Tuple[int, Placeholder]] = None): self.func_sources = func_sources - self.prototype_sources = {} if prototype_sources is None else prototype_sources self.exec_info = KernelExecutionInfo( entry_func_name=entry_func_name, threadgroups_per_grid=threadgroups_per_grid, threads_per_thread_group=threads_per_thread_group, - meta_buffer=meta_buffer + meta_buffer=meta_buffer, + unresolved_value_list=[] if unresolved_value_list is None else unresolved_value_list ) diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/average_pooling_2d.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/average_pooling_2d.py index dd51f590f..4a7abbe33 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/average_pooling_2d.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/average_pooling_2d.py @@ -1,31 +1,36 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.axis import Axis from webdnn.graph.operators.average_pooling_2d import AveragePooling2D from webdnn.graph.order import OrderNHWC template = """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *X = data_buffer + %%META_LOAD(average_pooling_2d_X_offset)%%; - device float *Y = data_buffer + %%META_LOAD(average_pooling_2d_Y_offset)%%; - const int N = %%META_LOAD(average_pooling_2d_N)%%; - const int H1 = %%META_LOAD(average_pooling_2d_H1)%%; - const int W1 = %%META_LOAD(average_pooling_2d_W1)%%; - const int C = %%META_LOAD(average_pooling_2d_C)%%; - const int H2 = %%META_LOAD(average_pooling_2d_H2)%%; - const int W2 = %%META_LOAD(average_pooling_2d_W2)%%; - const int K = %%META_LOAD(average_pooling_2d_K)%%; - const int S = %%META_LOAD(average_pooling_2d_S)%%; - const int P = %%META_LOAD(average_pooling_2d_P)%%; + const device float *X = %%LOAD_BUFFER(average_pooling_2d_X)%%; + device float *Y = %%LOAD_BUFFER(average_pooling_2d_Y)%%; + const int N = %%LOAD_BUFFER(average_pooling_2d_N)%%; + const int H1 = %%LOAD_BUFFER(average_pooling_2d_H1)%%; + const int W1 = %%LOAD_BUFFER(average_pooling_2d_W1)%%; + const int C = %%LOAD_BUFFER(average_pooling_2d_C)%%; + const int H2 = %%LOAD_BUFFER(average_pooling_2d_H2)%%; + const int W2 = %%LOAD_BUFFER(average_pooling_2d_W2)%%; + + const int KH = %%LOAD_BUFFER(average_pooling_2d_KH)%%; + const int KW = %%LOAD_BUFFER(average_pooling_2d_KW)%%; + const int SH = %%LOAD_BUFFER(average_pooling_2d_SH)%%; + const int SW = %%LOAD_BUFFER(average_pooling_2d_SW)%%; + const int PH = %%LOAD_BUFFER(average_pooling_2d_PH)%%; + const int PW = %%LOAD_BUFFER(average_pooling_2d_PW)%%; for (int gid = index; gid < N * H2 * W2 * C; gid += num_threads) { const int c = gid % C; @@ -34,18 +39,18 @@ const int n = gid / C / W2 / H2; float v = 0; - for (int kh = 0; kh < K; kh++) { - const int h1 = h2 * S - P + kh; + for (int kh = 0; kh < KH; kh++) { + const int h1 = h2 * SH - PH + kh; if (h1 < 0 || h1 >= H1) continue; - for (int kw = 0; kw < K; kw++) { - const int w1 = w2 * S - P + kw; + for (int kw = 0; kw < KW; kw++) { + const int w1 = w2 * SW - PW + kw; if (w1 < 0 || w1 >= W1) continue; v += X[((n * H1 + h1) * W1 + w1) * C + c]; } } - v /= K * K; + v /= KH * KW; Y[gid] = v; } @@ -54,41 +59,44 @@ def average_pooling_2d(op: AveragePooling2D, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.order == OrderNHWC assert y.variable.order == OrderNHWC - meta_injector = MetaInjector() - meta_injector.register({ - "average_pooling_2d_X_offset": x.offset, - "average_pooling_2d_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "average_pooling_2d_X": x, + "average_pooling_2d_Y": y, "average_pooling_2d_N": x.variable.shape_dict[Axis.N], "average_pooling_2d_H1": x.variable.shape_dict[Axis.H], "average_pooling_2d_W1": x.variable.shape_dict[Axis.W], "average_pooling_2d_C": x.variable.shape_dict[Axis.C], "average_pooling_2d_H2": y.variable.shape_dict[Axis.H], "average_pooling_2d_W2": y.variable.shape_dict[Axis.W], - "average_pooling_2d_K": op.parameters["ksize"][0], - "average_pooling_2d_S": op.parameters["stride"][0], - "average_pooling_2d_P": op.parameters["padding"][0], + "average_pooling_2d_KH": op.parameters["ksize"][0], + "average_pooling_2d_KW": op.parameters["ksize"][1], + "average_pooling_2d_SH": op.parameters["stride"][0], + "average_pooling_2d_SW": op.parameters["stride"][1], + "average_pooling_2d_PH": op.parameters["padding"][0], + "average_pooling_2d_PW": op.parameters["padding"][1], }) name_injector = KernelNameInjector(op) source = template - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/axiswise_bias.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/axiswise_bias.py index 157ad15d6..6b73f7268 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/axiswise_bias.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/axiswise_bias.py @@ -1,50 +1,49 @@ from typing import List -import numpy as np - -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.operators.axiswise_bias import AxiswiseBias +from webdnn.util.misc import mul def axiswise_bias(op: AxiswiseBias, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] if x.variable.order == y.variable.order: - return axiswise_bias_same_order(op, constants_layout, variables_layout) + return axiswise_bias_same_order(op, memory_layout) else: - return axiswise_bias_general(op, constants_layout, variables_layout) + return axiswise_bias_general(op, memory_layout) def generate_template_same_order(D1, D3): return """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { #define FLAG_D1_EQUAL_1 %%FLAG_D1_EQUAL_1%% #define FLAG_D3_EQUAL_1 %%FLAG_D3_EQUAL_1%% - const device float *X = data_buffer + %%META_LOAD(axiswise_bias_X_offset)%%; - const device float *B = weight_buffer + %%META_LOAD(axiswise_bias_B_offset)%%; - device float *Y = data_buffer + %%META_LOAD(axiswise_bias_Y_offset)%%; + const device float *X = %%LOAD_BUFFER(axiswise_bias_X)%%; + const device float *B = %%LOAD_BUFFER(axiswise_bias_B)%%; + device float *Y = %%LOAD_BUFFER(axiswise_bias_Y)%%; #if !OPTIMIZE || !FLAG_D1_EQUAL_1 - const int D1 = %%META_LOAD(axiswise_bias_D1)%%; + const int D1 = %%LOAD_BUFFER(axiswise_bias_D1)%%; #endif - const int D2 = %%META_LOAD(axiswise_bias_D2)%%; + const int D2 = %%LOAD_BUFFER(axiswise_bias_D2)%%; #if !OPTIMIZE || !FLAG_D3_EQUAL_1 - const int D3 = %%META_LOAD(axiswise_bias_D3)%%; + const int D3 = %%LOAD_BUFFER(axiswise_bias_D3)%%; #endif #if OPTIMIZE && FLAG_D3_EQUAL_1 @@ -83,22 +82,21 @@ def generate_template_same_order(D1, D3): def axiswise_bias_same_order(op: AxiswiseBias, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - b = constants_layout[op.inputs["b"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + b = memory_layout[op.inputs["b"]] + y = memory_layout[op.outputs["y"]] target_axis_index = x.variable.order.axes_dict[op.axis] - D1 = int(np.prod(x.variable.shape[:target_axis_index])) + D1 = mul(x.variable.shape[:target_axis_index]) D2 = x.variable.shape[target_axis_index] - D3 = int(np.prod(x.variable.shape[target_axis_index + 1:])) + D3 = mul(x.variable.shape[target_axis_index + 1:]) - meta_injector = MetaInjector() - meta_injector.register({ - "axiswise_bias_X_offset": x.offset, - "axiswise_bias_B_offset": b.offset, - "axiswise_bias_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "axiswise_bias_X": x, + "axiswise_bias_B": b, + "axiswise_bias_Y": y, "axiswise_bias_D1": D1, "axiswise_bias_D2": D2, "axiswise_bias_D3": D3 @@ -107,34 +105,35 @@ def axiswise_bias_same_order(op: AxiswiseBias, name_injector = KernelNameInjector(op) source = generate_template_same_order(D1, D3) - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] template_general = """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *X = data_buffer + %%META_LOAD(axiswise_bias_X_offset)%%; - const device float *B = weight_buffer + %%META_LOAD(axiswise_bias_B_offset)%%; - device float *Y = data_buffer + %%META_LOAD(axiswise_bias_Y_offset)%%; - const int D = %%META_LOAD(axiswise_bias_D)%%; - const int d_target = %%META_LOAD(axiswise_bias_d_target)%%; - const device int *x_shape = &(%%META_LOAD(axiswise_bias_x_shape)%%); - const device int *x_stride_in_y = &(%%META_LOAD(axiswise_bias_x_stride_in_y)%%); + const device float *X = %%LOAD_BUFFER(axiswise_bias_X)%%; + const device float *B = %%LOAD_BUFFER(axiswise_bias_B)%%; + device float *Y = %%LOAD_BUFFER(axiswise_bias_Y)%%; + const int D = %%LOAD_BUFFER(axiswise_bias_D)%%; + const int d_target = %%LOAD_BUFFER(axiswise_bias_d_target)%%; + const device int *x_shape = %%LOAD_BUFFER(axiswise_bias_x_shape)%%; + const device int *x_stride_in_y = %%LOAD_BUFFER(axiswise_bias_x_stride_in_y)%%; int size = 1; for (int d = 0; d < D; d++) size *= x_shape[d]; @@ -167,11 +166,10 @@ def axiswise_bias_same_order(op: AxiswiseBias, def axiswise_bias_general(op: AxiswiseBias, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - b = constants_layout[op.inputs["b"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + b = memory_layout[op.inputs["b"]] + y = memory_layout[op.outputs["y"]] x_shape = x.variable.shape @@ -183,29 +181,30 @@ def axiswise_bias_general(op: AxiswiseBias, x_stride_in_y = [y_strides[y.variable.order.axes_dict[axis]] for axis in x.variable.order.axes] - meta_injector = MetaInjector() - meta_injector.register({ - "axiswise_bias_X_offset": x.offset, - "axiswise_bias_B_offset": b.offset, - "axiswise_bias_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "axiswise_bias_X": x, + "axiswise_bias_B": b, + "axiswise_bias_Y": y, "axiswise_bias_D": x.variable.ndim, "axiswise_bias_d_target": x.variable.order.axes_dict[op.axis], - "axiswise_bias_x_shape": np.array(x_shape, dtype=np.int32).tobytes(), - "axiswise_bias_x_stride_in_y": np.array(x_stride_in_y, dtype=np.int32).tobytes(), + "axiswise_bias_x_shape": x_shape, + "axiswise_bias_x_stride_in_y": x_stride_in_y, }) name_injector = KernelNameInjector(op) source = template_general - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/axiswise_scale.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/axiswise_scale.py index acc2316a8..7f98b4b47 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/axiswise_scale.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/axiswise_scale.py @@ -1,50 +1,49 @@ from typing import List -import numpy as np - -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.operators.axiswise_scale import AxiswiseScale +from webdnn.util.misc import mul def axiswise_scale(op: AxiswiseScale, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] if x.variable.order == y.variable.order: - return axiswise_scale_same_order(op, constants_layout, variables_layout) + return axiswise_scale_same_order(op, memory_layout) else: - return axiswise_scale_general(op, constants_layout, variables_layout) + return axiswise_scale_general(op, memory_layout) def generate_template_same_order(D1, D3): return """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { #define FLAG_D1_EQUAL_1 %%FLAG_D1_EQUAL_1%% #define FLAG_D3_EQUAL_1 %%FLAG_D3_EQUAL_1%% - const device float *X = data_buffer + %%META_LOAD(axiswise_scale_X_offset)%%; - const device float *S = weight_buffer + %%META_LOAD(axiswise_scale_S_offset)%%; - device float *Y = data_buffer + %%META_LOAD(axiswise_scale_Y_offset)%%; + const device float *X = %%LOAD_BUFFER(axiswise_scale_X)%%; + const device float *S = %%LOAD_BUFFER(axiswise_scale_S)%%; + device float *Y = %%LOAD_BUFFER(axiswise_scale_Y)%%; #if !OPTIMIZE || !FLAG_D1_EQUAL_1 - const int D1 = %%META_LOAD(axiswise_scale_D1)%%; + const int D1 = %%LOAD_BUFFER(axiswise_scale_D1)%%; #endif - const int D2 = %%META_LOAD(axiswise_scale_D2)%%; + const int D2 = %%LOAD_BUFFER(axiswise_scale_D2)%%; #if !OPTIMIZE || !FLAG_D3_EQUAL_1 - const int D3 = %%META_LOAD(axiswise_scale_D3)%%; + const int D3 = %%LOAD_BUFFER(axiswise_scale_D3)%%; #endif #if OPTIMIZE && FLAG_D3_EQUAL_1 @@ -83,22 +82,21 @@ def generate_template_same_order(D1, D3): def axiswise_scale_same_order(op: AxiswiseScale, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - s = constants_layout[op.inputs["s"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + s = memory_layout[op.inputs["s"]] + y = memory_layout[op.outputs["y"]] target_axis_index = x.variable.order.axes_dict[op.axis] - D1 = int(np.prod(x.variable.shape[:target_axis_index])) + D1 = mul(x.variable.shape[:target_axis_index]) D2 = x.variable.shape[target_axis_index] - D3 = int(np.prod(x.variable.shape[target_axis_index + 1:])) + D3 = mul(x.variable.shape[target_axis_index + 1:]) - meta_injector = MetaInjector() - meta_injector.register({ - "axiswise_scale_X_offset": x.offset, - "axiswise_scale_S_offset": s.offset, - "axiswise_scale_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "axiswise_scale_X": x, + "axiswise_scale_S": s, + "axiswise_scale_Y": y, "axiswise_scale_D1": D1, "axiswise_scale_D2": D2, "axiswise_scale_D3": D3 @@ -107,34 +105,35 @@ def axiswise_scale_same_order(op: AxiswiseScale, name_injector = KernelNameInjector(op) source = generate_template_same_order(D1, D3) - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] template_general = """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *X = data_buffer + %%META_LOAD(axiswise_scale_X_offset)%%; - const device float *S = weight_buffer + %%META_LOAD(axiswise_scale_S_offset)%%; - device float *Y = data_buffer + %%META_LOAD(axiswise_scale_Y_offset)%%; - const int D = %%META_LOAD(axiswise_scale_D)%%; - const int d_target = %%META_LOAD(axiswise_scale_d_target)%%; - const device int *x_shape = &(%%META_LOAD(axiswise_scale_x_shape)%%); - const device int *x_stride_in_y = &(%%META_LOAD(axiswise_scale_x_stride_in_y)%%); + const device float *X = %%LOAD_BUFFER(axiswise_scale_X)%%; + const device float *S = %%LOAD_BUFFER(axiswise_scale_S)%%; + device float *Y = %%LOAD_BUFFER(axiswise_scale_Y)%%; + const int D = %%LOAD_BUFFER(axiswise_scale_D)%%; + const int d_target = %%LOAD_BUFFER(axiswise_scale_d_target)%%; + const device int *x_shape = %%LOAD_BUFFER(axiswise_scale_x_shape)%%; + const device int *x_stride_in_y = %%LOAD_BUFFER(axiswise_scale_x_stride_in_y)%%; int size = 1; for (int d = 0; d < D; d++) size *= x_shape[d]; @@ -167,11 +166,10 @@ def axiswise_scale_same_order(op: AxiswiseScale, def axiswise_scale_general(op: AxiswiseScale, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - s = constants_layout[op.inputs["s"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + s = memory_layout[op.inputs["s"]] + y = memory_layout[op.outputs["y"]] x_shape = x.variable.shape @@ -183,29 +181,30 @@ def axiswise_scale_general(op: AxiswiseScale, x_stride_in_y = [y_strides[y.variable.order.axes_dict[axis]] for axis in x.variable.order.axes] - meta_injector = MetaInjector() - meta_injector.register({ - "axiswise_scale_X_offset": x.offset, - "axiswise_scale_S_offset": s.offset, - "axiswise_scale_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "axiswise_scale_X": x, + "axiswise_scale_S": s, + "axiswise_scale_Y": y, "axiswise_scale_D": x.variable.ndim, "axiswise_scale_d_target": x.variable.order.axes_dict[op.axis], - "axiswise_scale_x_shape": np.array(x_shape, dtype=np.int32).tobytes(), - "axiswise_scale_x_stride_in_y": np.array(x_stride_in_y, dtype=np.int32).tobytes(), + "axiswise_scale_x_shape": x_shape, + "axiswise_scale_x_stride_in_y": x_stride_in_y, }) name_injector = KernelNameInjector(op) source = template_general - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/clipped_relu.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/clipped_relu.py new file mode 100644 index 000000000..0b0669667 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/clipped_relu.py @@ -0,0 +1,62 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.operators.clipped_relu import ClippedRelu + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%%[[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *X = %%LOAD_BUFFER(clipped_relu_X)%%; + device float *Y = %%LOAD_BUFFER(clipped_relu_Y)%%; + + const int N = %%LOAD_BUFFER(clipped_relu_N)%%; + const float cap = *((const device float *)(& %%LOAD_BUFFER(clipped_relu_cap)%%)); + + for (int gid = index; gid < N; gid += num_threads) { + float val = X[gid]; + + Y[gid] = val >= 0.0f ? (val < cap ? val : cap) : 0.0f; + } +} +""" + + +def clipped_relu(op: ClippedRelu, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "clipped_relu_X": x, + "clipped_relu_Y": y, + "clipped_relu_N": y.variable.size, + "clipped_relu_cap": op.parameters["cap"] + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/col2im.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/col2im.py index a67fcfe1a..aa01f98cc 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/col2im.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/col2im.py @@ -1,10 +1,11 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webgpu.kernel import GPUSize, Kernel from webdnn.backend.webgpu.operators.col2im import Col2Im +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.axis import Axis from webdnn.graph.order import OrderNHWC @@ -14,27 +15,27 @@ # C2, H2, W2などはすべてDeconvのinput, Convのoutputのサイズを表すために使用 template = """ -kernel void %%FUNC_NAME%%(const device float *param_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *col = data_buffer + %%META_LOAD(col2im_col_offset)%%; - device float *im = data_buffer + %%META_LOAD(col2im_im_offset)%%; + const device float *col = %%LOAD_BUFFER(col2im_col)%%; + device float *im = %%LOAD_BUFFER(col2im_im)%%; - const int N = %%META_LOAD(col2im_N)%%; - const int C1 = %%META_LOAD(col2im_C1)%%; - const int H1 = %%META_LOAD(col2im_H1)%%; - const int W1 = %%META_LOAD(col2im_W1)%%; - const int H2 = %%META_LOAD(col2im_H2)%%; - const int W2 = %%META_LOAD(col2im_W2)%%; - const int KH = %%META_LOAD(col2im_KH)%%; - const int KW = %%META_LOAD(col2im_KW)%%; - const int SH = %%META_LOAD(col2im_SH)%%; - const int SW = %%META_LOAD(col2im_SW)%%; - const int PH = %%META_LOAD(col2im_PH)%%; - const int PW = %%META_LOAD(col2im_PW)%%; + const int N = %%LOAD_BUFFER(col2im_N)%%; + const int C1 = %%LOAD_BUFFER(col2im_C1)%%; + const int H1 = %%LOAD_BUFFER(col2im_H1)%%; + const int W1 = %%LOAD_BUFFER(col2im_W1)%%; + const int H2 = %%LOAD_BUFFER(col2im_H2)%%; + const int W2 = %%LOAD_BUFFER(col2im_W2)%%; + const int KH = %%LOAD_BUFFER(col2im_KH)%%; + const int KW = %%LOAD_BUFFER(col2im_KW)%%; + const int SH = %%LOAD_BUFFER(col2im_SH)%%; + const int SW = %%LOAD_BUFFER(col2im_SW)%%; + const int PH = %%LOAD_BUFFER(col2im_PH)%%; + const int PW = %%LOAD_BUFFER(col2im_PW)%%; for (int gid = index; gid < N*H1*W1*C1; gid += num_threads) { const int c1 = gid % C1; @@ -64,18 +65,17 @@ # noinspection PyUnusedLocal def col2im(op: Col2Im, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - col = variables_layout[op.inputs["col"]] - im = variables_layout[op.outputs["im"]] + memory_layout: MemoryLayout) -> List[Kernel]: + col = memory_layout[op.inputs["col"]] + im = memory_layout[op.outputs["im"]] assert col.variable.order == OrderNHWC assert im.variable.order == OrderNHWC - meta_injector = MetaInjector() - meta_injector.register({ - "col2im_im_offset": im.offset, - "col2im_col_offset": col.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "col2im_im": im, + "col2im_col": col, "col2im_N": col.variable.shape_dict[Axis.N], "col2im_H2": col.variable.shape_dict[Axis.H], "col2im_W2": col.variable.shape_dict[Axis.W], @@ -93,15 +93,16 @@ def col2im(op: Col2Im, name_injector = KernelNameInjector(op) source = template - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/concat.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/concat.py index 0052e04b2..a41f886b1 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/concat.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/concat.py @@ -1,35 +1,33 @@ from typing import List -import numpy as np - -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.operators.concat import Concat template = """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - device float *y = data_buffer + %%META_LOAD(concat_y_offset)%%; - const char N = %%META_LOAD(concat_N)%%; - const char D = %%META_LOAD(concat_D)%%; - const device int *x_offsets = &(%%META_LOAD(concat_x_offsets)%%); - const device int *y_offsets = &(%%META_LOAD(concat_y_offsets)%%); - const device int *x_shapes = &(%%META_LOAD(concat_x_shapes)%%); - const device int *x_strides_in_y = &(%%META_LOAD(concat_x_strides_in_y)%%); + device float *y = %%LOAD_BUFFER(concat_y)%%; + const char N = %%LOAD_BUFFER(concat_N)%%; + const char D = %%LOAD_BUFFER(concat_D)%%; + const device int *y_offsets = %%LOAD_BUFFER(concat_y_offsets)%%; + const device int *x_shapes = %%LOAD_BUFFER(concat_x_shapes)%%; + const device int *x_strides_in_y = %%LOAD_BUFFER(concat_x_strides_in_y)%%; int x_index = index; for (int n = 0; n < N; n++) { - const device float *x = data_buffer + x_offsets[n]; + const device float *x = %%LOAD_BUFFER(concat_xs, n)%%; const int y_offset = y_offsets[n]; - const device int *x_shape = &(x_shapes[n*D]); - const device int *x_stride_in_y = &(x_strides_in_y[n*D]); + const device int *x_shape = x_shapes + n*D; + const device int *x_stride_in_y = x_strides_in_y + n*D; int x_size = 1; for (int d = 0; d < D; d++) { @@ -57,10 +55,9 @@ # noinspection PyUnusedLocal def concat(op: Concat, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - xs = [variables_layout[op.inputs[f"x{str(i)}"]] for i in range(len(op.inputs))] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + xs = [memory_layout[op.inputs[f"x{str(i)}"]] for i in range(len(op.inputs))] + y = memory_layout[op.outputs["y"]] target_axis = op.axis x_offsets = [x.offset for x in xs] @@ -85,29 +82,30 @@ def concat(op: Concat, y_offsets.append(target_axis_offset * y_strides[y.variable.order.axes_dict[target_axis]]) target_axis_offset += x.variable.shape_dict[target_axis] - meta_injector = MetaInjector() - meta_injector.register({ - "concat_y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "concat_y": y, "concat_D": len(y.variable.shape), "concat_N": len(xs), - "concat_x_offsets": np.array(x_offsets, dtype=np.int32).tobytes(), - "concat_x_strides_in_y": np.array(x_strides_in_y, dtype=np.int32).tobytes(), - "concat_x_shapes": np.array(x_shapes, dtype=np.int32).tobytes(), - "concat_y_offsets": np.array(y_offsets, dtype=np.int32).tobytes(), + "concat_xs": xs, + "concat_x_strides_in_y": x_strides_in_y, + "concat_x_shapes": x_shapes, + "concat_y_offsets": y_offsets }) name_injector = KernelNameInjector(op) source = template - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/elementwise_sum.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/elementwise_sum.py index 816efec8b..c739b4148 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/elementwise_sum.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/elementwise_sum.py @@ -1,18 +1,19 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.inline_injector import InlineInjector -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.inline_injector import InlineInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.operators.axiswise_scale import AxiswiseScale def generate_template(N, has_inline): return """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { @@ -27,15 +28,15 @@ def generate_template(N, has_inline): #if OPTIMIZE && N_DIVIDABLE_BY_4 - const device float4 *X0 = (const device float4 *)(data_buffer + %%META_LOAD(elementwise_sum_X0_offset)%%); - const device float4 *X1 = (const device float4 *)(data_buffer + %%META_LOAD(elementwise_sum_X1_offset)%%); - device float4 *Y = (device float4 *)(data_buffer + %%META_LOAD(elementwise_sum_Y_offset)%%); - const int N = (%%META_LOAD(elementwise_sum_N)%%) >> 2; + const device float4 *X0 = (const device float4 *)(%%LOAD_BUFFER(elementwise_sum_X0)%%); + const device float4 *X1 = (const device float4 *)(%%LOAD_BUFFER(elementwise_sum_X1)%%); + device float4 *Y = (device float4 *)(%%LOAD_BUFFER(elementwise_sum_Y)%%); + const int N = (%%LOAD_BUFFER(elementwise_sum_N)%%) >> 2; #else - const device float *X0 = data_buffer + %%META_LOAD(elementwise_sum_X0_offset)%%; - const device float *X1 = data_buffer + %%META_LOAD(elementwise_sum_X1_offset)%%; - device float *Y = data_buffer + %%META_LOAD(elementwise_sum_Y_offset)%%; - const int N = %%META_LOAD(elementwise_sum_N)%%; + const device float *X0 = %%LOAD_BUFFER(elementwise_sum_X0)%%; + const device float *X1 = %%LOAD_BUFFER(elementwise_sum_X1)%%; + device float *Y = %%LOAD_BUFFER(elementwise_sum_Y)%%; + const int N = %%LOAD_BUFFER(elementwise_sum_N)%%; #endif for (int gid = index; gid < N; gid += num_threads) { @@ -66,20 +67,19 @@ def generate_template(N, has_inline): def elementwise_sum(op: AxiswiseScale, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x0 = variables_layout[op.inputs["x0"]] - x1 = variables_layout[op.inputs["x1"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x0 = memory_layout[op.inputs["x0"]] + x1 = memory_layout[op.inputs["x1"]] + y = memory_layout[op.outputs["y"]] assert len(op.inputs) == 2, "[WebGPU] ElementwiseSum operator currently supported only 2 inputs." assert x0.variable.shape == x1.variable.shape == y.variable.shape - meta_injector = MetaInjector() - meta_injector.register({ - "elementwise_sum_X0_offset": x0.offset, - "elementwise_sum_X1_offset": x1.offset, - "elementwise_sum_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "elementwise_sum_X0": x0, + "elementwise_sum_X1": x1, + "elementwise_sum_Y": y, "elementwise_sum_N": y.variable.size }) @@ -87,7 +87,7 @@ def elementwise_sum(op: AxiswiseScale, name_injector = KernelNameInjector(op) source = generate_template(y.variable.size, inline_injector.has_inline) - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = inline_injector.inject(source) source = name_injector.inject(source) @@ -95,8 +95,9 @@ def elementwise_sum(op: AxiswiseScale, {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/elu.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/elu.py index 683206b0a..6ebc3d7ea 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/elu.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/elu.py @@ -1,22 +1,23 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.operators.elu import Elu template = """ -kernel void %%FUNC_NAME%%(const device float *param_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *X = data_buffer + %%META_LOAD(elu_X_offset)%%; - device float *Y = data_buffer + %%META_LOAD(elu_Y_offset)%%; + const device float *X = %%LOAD_BUFFER(elu_X)%%; + device float *Y = %%LOAD_BUFFER(elu_Y)%%; - const int N = %%META_LOAD(elu_N)%%; + const int N = %%LOAD_BUFFER(elu_N)%%; for (int gid = index; gid < N; gid += num_threads) { float result = X[gid]; @@ -29,32 +30,32 @@ def elu(op: Elu, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.shape == y.variable.shape - meta_injector = MetaInjector() - meta_injector.register({ - "elu_X_offset": x.offset, - "elu_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "elu_X": x, + "elu_Y": y, "elu_N": y.variable.size }) name_injector = KernelNameInjector(op) source = template - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/embedding.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/embedding.py new file mode 100644 index 000000000..2516f2dea --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/embedding.py @@ -0,0 +1,74 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.axis import Axis +from webdnn.graph.operators.embedding import Embedding +from webdnn.graph.order import OrderCN, OrderNT, OrderNTC + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], + ushort global_index[[thread_position_in_grid]], + ushort num_threads[[threads_per_grid]]) +{ + const device float *X = %%LOAD_BUFFER(embedding_X)%%; + device float *Y = %%LOAD_BUFFER(embedding_Y)%%; + const device float *W = %%LOAD_BUFFER(embedding_W)%%; + + const int T = %%LOAD_BUFFER(embedding_T)%%; + const int N = %%LOAD_BUFFER(embedding_N)%%; + const int C = %%LOAD_BUFFER(embedding_C)%%; + + for (int gid = global_index; gid < N * T; gid += num_threads) { + const int t = gid % T; + const int n = gid / T; + + const int word = (int)X[gid]; + for (int c = 0; c < C; c++) { + Y[(n * T + t) * C + c] = W[word * C + c]; + } + } +} +""" + + +def embedding(op: Embedding, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + w = memory_layout[op.inputs["w"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.order == OrderNT + assert w.variable.order == OrderCN + assert y.variable.order == OrderNTC + + buffer_injector = BufferInjector() + buffer_injector.register({ + "embedding_X": x, + "embedding_Y": y, + "embedding_W": w, + "embedding_T": x.variable.shape_dict[Axis.T], + "embedding_N": x.variable.shape_dict[Axis.N], + "embedding_C": w.variable.shape_dict[Axis.N] + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/flatten.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/flatten.py index ce6673803..cedb78619 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/flatten.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/flatten.py @@ -1,22 +1,23 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.operators.flatten import Flatten template = """ -kernel void %%FUNC_NAME%%(const device float *param_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *x = data_buffer + %%META_LOAD(flatten_x_offset)%%; - device float *y = data_buffer + %%META_LOAD(flatten_y_offset)%%; + const device float *x = %%LOAD_BUFFER(flatten_x)%%; + device float *y = %%LOAD_BUFFER(flatten_y)%%; - const int N = %%META_LOAD(flatten_N)%%; + const int N = %%LOAD_BUFFER(flatten_N)%%; for (int gid = index; gid < N; gid += num_threads) { y[gid] = x[gid]; @@ -26,32 +27,32 @@ def flatten(op: Flatten, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] # assert x.variable.order == y.variable.order - meta_injector = MetaInjector() - meta_injector.register({ - "flatten_x_offset": x.offset, - "flatten_y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "flatten_x": x, + "flatten_y": y, "flatten_N": y.variable.size, }) name_injector = KernelNameInjector(op) source = template - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/hard_sigmoid.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/hard_sigmoid.py new file mode 100644 index 000000000..8c3dd45b0 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/hard_sigmoid.py @@ -0,0 +1,66 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.operators.hard_sigmoid import HardSigmoid + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%%[[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *X = %%LOAD_BUFFER(hard_sigmoid_X)%%; + device float *Y = %%LOAD_BUFFER(hard_sigmoid_Y)%%; + + const int N = %%LOAD_BUFFER(hard_sigmoid_N)%%; + + for (int gid = index; gid < N; gid += num_threads) { + float result = X[gid]; + result = result * 0.2f + 0.5f; + if (result < 0.0f) { + result = 0.0f; + } else if (result > 1.0f) { + result = 1.0f; + } + + Y[gid] = result; + } +} +""" + + +def hard_sigmoid(op: HardSigmoid, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "hard_sigmoid_X": x, + "hard_sigmoid_Y": y, + "hard_sigmoid_N": y.variable.size + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/im2col.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/im2col.py index 2cc0ce43b..a5173d3a0 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/im2col.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/im2col.py @@ -1,8 +1,8 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webgpu.kernel import GPUSize, Kernel from webdnn.backend.webgpu.operators.im2col import Im2Col from webdnn.graph.axis import Axis @@ -11,9 +11,9 @@ def generate_template_NHWC(SH, SW, C1): return """ -kernel void %%FUNC_NAME%%(const device float *param_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], ushort index_thread[[thread_position_in_threadgroup]], ushort index_group[[threadgroup_position_in_grid]]) { @@ -23,31 +23,32 @@ def generate_template_NHWC(SH, SW, C1): #if OPTIMIZE && C1_DIVIDABLE_BY_4 - const device float4 *im4 = (const device float4 *)(data_buffer + %%META_LOAD(im2col_im_offset)%%); - device float4 *col4 = (device float4 *)(data_buffer + %%META_LOAD(im2col_col_offset)%%); - const int C1_4 = (%%META_LOAD(im2col_C1)%%) >> 2; + const device float4 *im4 = (const device float4 *)(%%LOAD_BUFFER(im2col_im)%%); + device float4 *col4 = (device float4 *)(%%LOAD_BUFFER(im2col_col)%%); + const int C1_4 = (%%LOAD_BUFFER(im2col_C1)%%) >> 2; #else - const device float *im = data_buffer + %%META_LOAD(im2col_im_offset)%%; - device float *col = data_buffer + %%META_LOAD(im2col_col_offset)%%; - const int C1 = %%META_LOAD(im2col_C1)%%; + const device float *im = %%LOAD_BUFFER(im2col_im)%%; + device float *col = %%LOAD_BUFFER(im2col_col)%%; + const int C1 = %%LOAD_BUFFER(im2col_C1)%%; #endif - // const int N = %%META_LOAD(im2col_N)%%; - const int H1 = %%META_LOAD(im2col_H1)%%; - const int W1 = %%META_LOAD(im2col_W1)%%; - const int H2 = %%META_LOAD(im2col_H2)%%; - const int W2 = %%META_LOAD(im2col_W2)%%; - const int KH = %%META_LOAD(im2col_KH)%%; - const int KW = %%META_LOAD(im2col_KW)%%; - const int PH = %%META_LOAD(im2col_PH)%%; - const int PW = %%META_LOAD(im2col_PW)%%; + const int H1 = %%LOAD_BUFFER(im2col_H1)%%; + const int W1 = %%LOAD_BUFFER(im2col_W1)%%; + const int H2 = %%LOAD_BUFFER(im2col_H2)%%; + const int W2 = %%LOAD_BUFFER(im2col_W2)%%; + const int KH = %%LOAD_BUFFER(im2col_KH)%%; + const int KW = %%LOAD_BUFFER(im2col_KW)%%; + const int DH = %%LOAD_BUFFER(im2col_DH)%%; + const int DW = %%LOAD_BUFFER(im2col_DW)%%; + const int PH = %%LOAD_BUFFER(im2col_PH)%%; + const int PW = %%LOAD_BUFFER(im2col_PW)%%; #if !OPTIMIZE || !SH_EQUAL_1 - const int SH = %%META_LOAD(im2col_SH)%%; + const int SH = %%LOAD_BUFFER(im2col_SH)%%; #endif #if !OPTIMIZE || !SW_EQUAL_1 - const int SW = %%META_LOAD(im2col_SW)%%; + const int SW = %%LOAD_BUFFER(im2col_SW)%%; #endif const int H1P = H1 + 2 * PH; @@ -67,19 +68,19 @@ def generate_template_NHWC(SH, SW, C1): #if OPTIMIZE && SH_EQUAL_1 for (int kh = 0; kh < KH; kh++) { - const int h2 = h1 + PH - kh; + const int h2 = h1 + PH - kh * DH; #else for (int kh = (h1 + PH) % SH; kh < KH; kh += SH) { - const int h2 = (h1 + PH - kh) / SH; + const int h2 = (h1 + PH - kh * DH) / SH; #endif if (h2 < 0 || h2 >= H2) continue; #if OPTIMIZE && SH_EQUAL_1 for (int kw = 0; kw < KW; kw++) { - const int w2 = w1 + PW - kw; + const int w2 = w1 + PW - kw * DW; #else for (int kw = (w1 + PW) % SW; kw < KW; kw += SW) { - const int w2 = (w1 + PW - kw) / SW; + const int w2 = (w1 + PW - kw * DW) / SW; #endif if (w2 < 0 || w2 >= W2) continue; @@ -104,27 +105,29 @@ def generate_template_NHWC(SH, SW, C1): template_CNHW = """ -kernel void %%FUNC_NAME%%(const device float *param_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *im = data_buffer + %%META_LOAD(im2col_im_offset)%%; - device float *col = data_buffer + %%META_LOAD(im2col_col_offset)%%; - - const int N = %%META_LOAD(im2col_N)%%; - const int C1 = %%META_LOAD(im2col_C1)%%; - const int H1 = %%META_LOAD(im2col_H1)%%; - const int W1 = %%META_LOAD(im2col_W1)%%; - const int H2 = %%META_LOAD(im2col_H2)%%; - const int W2 = %%META_LOAD(im2col_W2)%%; - const int KH = %%META_LOAD(im2col_KH)%%; - const int KW = %%META_LOAD(im2col_KW)%%; - const int SH = %%META_LOAD(im2col_SH)%%; - const int SW = %%META_LOAD(im2col_SW)%%; - const int PH = %%META_LOAD(im2col_PH)%%; - const int PW = %%META_LOAD(im2col_PW)%%; + const device float *im = %%LOAD_BUFFER(im2col_im)%%; + device float *col = %%LOAD_BUFFER(im2col_col)%%; + + const int N = %%LOAD_BUFFER(im2col_N)%%; + const int C1 = %%LOAD_BUFFER(im2col_C1)%%; + const int H1 = %%LOAD_BUFFER(im2col_H1)%%; + const int W1 = %%LOAD_BUFFER(im2col_W1)%%; + const int H2 = %%LOAD_BUFFER(im2col_H2)%%; + const int W2 = %%LOAD_BUFFER(im2col_W2)%%; + const int KH = %%LOAD_BUFFER(im2col_KH)%%; + const int KW = %%LOAD_BUFFER(im2col_KW)%%; + const int DH = %%LOAD_BUFFER(im2col_DH)%%; + const int DW = %%LOAD_BUFFER(im2col_DW)%%; + const int SH = %%LOAD_BUFFER(im2col_SH)%%; + const int SW = %%LOAD_BUFFER(im2col_SW)%%; + const int PH = %%LOAD_BUFFER(im2col_PH)%%; + const int PW = %%LOAD_BUFFER(im2col_PW)%%; for (int gid = index; gid < N*H2*W2*KH*KW*C1; gid += num_threads) { const int w2 = gid % W2; @@ -134,8 +137,8 @@ def generate_template_NHWC(SH, SW, C1): const int kw = gid / W2 / H2 / N / C1 % KW; const int kh = gid / W2 / H2 / N / C1 / KW; - const int h1 = h2 * SH - PH + kh; - const int w1 = w2 * SW - PW + kw; + const int h1 = h2 * SH - PH + kh * DH; + const int w1 = w2 * SW - PW + kw * DW; col[gid] = (h1 < 0 || h1 >= H1 || w1 < 0 || w1 >= W1) ? 0 : im[((n*H1+h1)*W1+w1)*C1+c1]; } @@ -144,10 +147,9 @@ def generate_template_NHWC(SH, SW, C1): def im2col(op: Im2Col, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - im = variables_layout[op.inputs["im"]] - col = variables_layout[op.outputs["col"]] + memory_layout: MemoryLayout) -> List[Kernel]: + im = memory_layout[op.inputs["im"]] + col = memory_layout[op.outputs["col"]] assert im.variable.order == OrderNHWC assert col.variable.order == OrderNHWC or col.variable.order == OrderCNHW @@ -160,11 +162,11 @@ def im2col(op: Im2Col, H1P = H1 + 2 * op.PH W1P = W1 + 2 * op.PW - meta_injector = MetaInjector() - meta_injector.register({ - "im2col_im_offset": im.offset, - "im2col_col_offset": col.offset, - "im2col_N": col.variable.shape_dict[Axis.N], + buffer_injector = BufferInjector() + buffer_injector.register({ + "im2col_im": im, + "im2col_col": col, + "im2col_N": N, "im2col_C1": C1, "im2col_H1": im.variable.shape_dict[Axis.H], "im2col_W1": im.variable.shape_dict[Axis.W], @@ -172,6 +174,8 @@ def im2col(op: Im2Col, "im2col_W2": col.variable.shape_dict[Axis.W], "im2col_KH": op.KH, "im2col_KW": op.KW, + "im2col_DH": op.DH, + "im2col_DW": op.DW, "im2col_SH": op.SH, "im2col_SW": op.SW, "im2col_PH": op.PH, @@ -181,7 +185,7 @@ def im2col(op: Im2Col, name_injector = KernelNameInjector(op) source = template_CNHW if col.variable.order == OrderCNHW else generate_template_NHWC(op.SH, op.SW, C1) - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( @@ -189,7 +193,8 @@ def im2col(op: Im2Col, name_injector.name, GPUSize(N * H1P * W1P, 1, 1), GPUSize(64, 1, 1), - meta_injector.buffer + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/leaky_relu.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/leaky_relu.py new file mode 100644 index 000000000..43a18ab58 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/leaky_relu.py @@ -0,0 +1,62 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.operators.leaky_relu import LeakyRelu + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%%[[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *X = %%LOAD_BUFFER(leaky_relu_X)%%; + device float *Y = %%LOAD_BUFFER(leaky_relu_Y)%%; + + const int N = %%LOAD_BUFFER(leaky_relu_N)%%; + const float slope = *((const device float *)(& %%LOAD_BUFFER(leaky_relu_slope)%%)); + + for (int gid = index; gid < N; gid += num_threads) { + float val = X[gid]; + + Y[gid] = val >= 0.0f ? val : val * slope; + } +} +""" + + +def leaky_relu(op: LeakyRelu, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "leaky_relu_X": x, + "leaky_relu_Y": y, + "leaky_relu_N": y.variable.size, + "leaky_relu_slope": op.parameters["slope"] + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/local_response_normalization.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/local_response_normalization.py index 8e8c6de39..0d4aa4c55 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/local_response_normalization.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/local_response_normalization.py @@ -1,45 +1,44 @@ from typing import List -import numpy as np - -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.axis import Axis from webdnn.graph.operators.local_response_normalization import LocalResponseNormalization +from webdnn.util.misc import mul def local_response_normalization(op: LocalResponseNormalization, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] if x.variable.order == y.variable.order: - return local_response_normalization_same_order(op, constants_layout, variables_layout) + return local_response_normalization_same_order(op, memory_layout) else: - return local_response_normalization_general(op, constants_layout, variables_layout) + return local_response_normalization_general(op, memory_layout) template_same_order = """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *X = data_buffer + %%META_LOAD(local_response_normalization_X_offset)%%; - device float *Y = data_buffer + %%META_LOAD(local_response_normalization_Y_offset)%%; - const int D1 = %%META_LOAD(local_response_normalization_D1)%%; - const int D2 = %%META_LOAD(local_response_normalization_D2)%%; - const int D3 = %%META_LOAD(local_response_normalization_D3)%%; + const device float *X = %%LOAD_BUFFER(local_response_normalization_X)%%; + device float *Y = %%LOAD_BUFFER(local_response_normalization_Y)%%; + const int D1 = %%LOAD_BUFFER(local_response_normalization_D1)%%; + const int D2 = %%LOAD_BUFFER(local_response_normalization_D2)%%; + const int D3 = %%LOAD_BUFFER(local_response_normalization_D3)%%; - const int Phalfn = %%META_LOAD(local_response_normalization_param_half_n)%%; - const float Pk = *((const device float *)(& %%META_LOAD(local_response_normalization_param_k)%%)); - const float Palpha = *((const device float *)(& %%META_LOAD(local_response_normalization_param_alpha)%%)); - const float Pmbeta = *((const device float *)(& %%META_LOAD(local_response_normalization_param_minus_beta)%%)); + const int Phalfn = %%LOAD_BUFFER(local_response_normalization_param_half_n)%%; + const float Pk = *((const device float *)(& %%LOAD_BUFFER(local_response_normalization_param_k)%%)); + const float Palpha = *((const device float *)(& %%LOAD_BUFFER(local_response_normalization_param_alpha)%%)); + const float Pmbeta = *((const device float *)(& %%LOAD_BUFFER(local_response_normalization_param_minus_beta)%%)); for (int gid = index; gid < D1 * D2 * D3; gid += num_threads) { const int d3 = gid % D3; @@ -65,21 +64,20 @@ def local_response_normalization(op: LocalResponseNormalization, def local_response_normalization_same_order(op: LocalResponseNormalization, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] target_axis = Axis.C # FIXME target_axis_index = x.variable.order.axes_dict[target_axis] - D1 = int(np.prod(x.variable.shape[:target_axis_index])) + D1 = mul(x.variable.shape[:target_axis_index]) D2 = x.variable.shape[target_axis_index] - D3 = int(np.prod(x.variable.shape[target_axis_index + 1:])) + D3 = mul(x.variable.shape[target_axis_index + 1:]) - meta_injector = MetaInjector() - meta_injector.register({ - "local_response_normalization_X_offset": x.offset, - "local_response_normalization_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "local_response_normalization_X": x, + "local_response_normalization_Y": y, "local_response_normalization_D1": D1, "local_response_normalization_D2": D2, "local_response_normalization_D3": D3, @@ -92,24 +90,25 @@ def local_response_normalization_same_order(op: LocalResponseNormalization, name_injector = KernelNameInjector(op) source = template_same_order - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] template_general = """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { @@ -168,10 +167,9 @@ def local_response_normalization_same_order(op: LocalResponseNormalization, def local_response_normalization_general(op: LocalResponseNormalization, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] target_axis = Axis.C @@ -185,14 +183,14 @@ def local_response_normalization_general(op: LocalResponseNormalization, x_stride_in_y = [y_strides[y.variable.order.axes_dict[axis]] for axis in x.variable.order.axes] - meta_injector = MetaInjector() - meta_injector.register({ - "local_response_normalization_X_offset": x.offset, - "local_response_normalization_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "local_response_normalization_X": x, + "local_response_normalization_Y": y, "local_response_normalization_D": x.variable.ndim, "local_response_normalization_d_target": x.variable.order.axes_dict[target_axis], - "local_response_normalization_x_shape": np.array(x_shape, dtype=np.int32).tobytes(), - "local_response_normalization_x_stride_in_y": np.array(x_stride_in_y, dtype=np.int32).tobytes(), + "local_response_normalization_x_shape": x_shape, + "local_response_normalization_x_stride_in_y": x_stride_in_y, "local_response_normalization_param_half_n": int(op.parameters["n"] // 2), "local_response_normalization_param_k": float(op.parameters["k"]), "local_response_normalization_param_alpha": float(op.parameters["alpha"]), @@ -202,15 +200,16 @@ def local_response_normalization_general(op: LocalResponseNormalization, name_injector = KernelNameInjector(op) source = template_general - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/lstm.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/lstm.py new file mode 100644 index 000000000..2c5aea4fa --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/lstm.py @@ -0,0 +1,221 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.axis import Axis +from webdnn.graph.operators.lstm import LSTM +from webdnn.graph.order import OrderNC, OrderNTC, OrderCN + + +def generate_template_general(initial_C: bool, initial_H: bool, return_sequences: bool, + activation_function: str, recurrent_activation_function: str): + return """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%%[[buffer(2)]], + uint global_index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ +#define USE_INITIAL_C %%USE_INITIAL_C%% +#define USE_INITIAL_H %%USE_INITIAL_H%% +#define activation_function(x) %%ACTIVATION_FUNCTION%% +#define recurrent_activation_function(x) %%RECURRENT_ACTIVATION_FUNCTION%% +#define RETURN_SEQUENCES %%RETURN_SEQUENCES%% + + const device float *X = %%LOAD_BUFFER(lstm_X)%%; + device float *XH = %%LOAD_BUFFER(lstm_X_and_H)%%; + const device float *W_all = %%LOAD_BUFFER(lstm_W_all)%%; + device float *workspace = %%LOAD_BUFFER(lstm_workspace)%%; + device float *Y = %%LOAD_BUFFER(lstm_Y)%%; + device float *final_C = %%LOAD_BUFFER(lstm_final_C)%%; + const device float *b = %%LOAD_BUFFER(lstm_b)%%; + +#if USE_INITIAL_C + const device float *initial_C = %%LOAD_BUFFER(lstm_initial_C)%%; +#endif +#if USE_INITIAL_H + const device float *initial_H = %%LOAD_BUFFER(lstm_initial_H)%%; +#endif + + const int N = %%LOAD_BUFFER(lstm_N)%%; + const int T = %%LOAD_BUFFER(lstm_T)%%; + const int C1 = %%LOAD_BUFFER(lstm_C1)%%; + const int C2 = %%LOAD_BUFFER(lstm_C2)%%; + + device float *XH_X = XH; + device float *XH_H = XH + C1 * N; + + //reset output and cell state + for (int gid = global_index; gid < N * C2; gid += num_threads) + { +#if USE_INITIAL_H + XH_H[gid] = initial_H[gid]; +#else + XH_H[gid] = 0; +#endif + +#if USE_INITIAL_C + final_C[gid] = initial_C[gid]; +#else + final_C[gid] = 0; +#endif + } + + for (int t = 0; t < T; t++) + { + for (int gid = global_index; gid < C1 * N; gid += num_threads) + { + const int n = gid % N; + const int c1 = gid / N; + XH_X[gid] = X[(n * T + t) * C1 + c1]; + } + + threadgroup_barrier(mem_flags::mem_device); + + //FIXME: replace here to more efficient sgemv implementation. + for (int gid = global_index; gid < C2 * 4 * N; gid += num_threads) + { + const int n = gid % N; + const int c2_4 = gid / N; + + float v = b[c2_4]; + + for (int c1c2 = 0; c1c2 < C1 + C2; c1c2++) + { + v += XH[c1c2 * N + n] * W_all[c1c2 * C2 * 4 + c2_4]; + } + + workspace[gid] = v; + } + + threadgroup_barrier(mem_flags::mem_device); + + for (int gid = global_index; gid < C2 * N; gid += num_threads) + { + float i = workspace[gid + N * C2 * 0]; + float f = workspace[gid + N * C2 * 1]; + float a = workspace[gid + N * C2 * 2]; + float o = workspace[gid + N * C2 * 3]; + float c = final_C[gid]; + + i = recurrent_activation_function(i); + f = recurrent_activation_function(f); + a = activation_function(a); + o = recurrent_activation_function(o); + + c = a * i + c * f; + + final_C[gid] = c; + const float h = activation_function(c) * o; + XH_H[gid] = h; + +#if RETURN_SEQUENCES + const int n = gid % N; + const int c2 = gid / N; + Y[(n * T + t) * C2 + c2] = h; +#endif + } + } + +#if !RETURN_SEQUENCES + //copy final output to output variable + for (int gid = global_index; gid < C2 * N; gid += num_threads) + { + Y[gid] = XH_H[gid]; + } +#endif + +#undef USE_INITIAL_C +#undef USE_INITIAL_H +#undef activation_function +#undef recurrent_activation_function +#undef RETURN_SEQUENCES +} + """ \ + .replace("%%USE_INITIAL_C%%", "1" if initial_C else "0") \ + .replace("%%USE_INITIAL_H%%", "1" if initial_H else "0") \ + .replace("%%ACTIVATION_FUNCTION%%", activation_function) \ + .replace("%%RECURRENT_ACTIVATION_FUNCTION%%", recurrent_activation_function) \ + .replace("%%RETURN_SEQUENCES%%", "1" if return_sequences else "0") + + +def lstm(op: LSTM, memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + b = memory_layout[op.inputs["b"]] + y = memory_layout[op.outputs["y"]] + x_and_h = memory_layout[op.inputs["x_and_h"]] + w_all = memory_layout[op.inputs["w_all"]] + workspace = memory_layout[op.inputs["workspace"]] + final_c = memory_layout[op.outputs["final_c"]] + + use_initial_c = op.parameters["use_initial_c"] + use_initial_h = op.parameters["use_initial_h"] + return_sequences = op.parameters["return_sequences"] + + assert x.variable.order == OrderNTC, \ + f"Current implementation supports only OrderNTC for input variable order: x.order = {x.variable.order}" + + if return_sequences: + assert y.variable.order == OrderNTC, f"Current implementation supports only OrderNTC for output variable of " + \ + f"LSTM in return_sequences=True mode: y.order = {y.variable.order}" + else: + assert y.variable.order == OrderNC, \ + f"Current implementation supports only OrderNC for output variable of LSTM " + \ + f"in return_sequences=False mode: y.order = {y.variable.order}" + + assert w_all.variable.order == OrderCN + + N = x.variable.shape_dict[Axis.N] + T = x.variable.shape_dict[Axis.T] + C1 = x.variable.shape_dict[Axis.C] + C2 = y.variable.shape_dict[Axis.C] + + buffer_injector = BufferInjector() + buffer_injector.register({ + "lstm_X": x, + "lstm_Y": y, + "lstm_b": b, + "lstm_N": N, + "lstm_T": T, + "lstm_C1": C1, + "lstm_C2": C2, + "lstm_X_and_H": x_and_h, + "lstm_W_all": w_all, + "lstm_workspace": workspace, + "lstm_final_C": final_c, + "lstm_initial_C": memory_layout[op.inputs["initial_c"]] if use_initial_c else 0, + "lstm_initial_H": memory_layout[op.inputs["initial_h"]] if use_initial_h else 0, + }) + + name_injector = KernelNameInjector(op) + + if op.parameters["activation"] == "tanh": + activation_function = "(tanh(x))" + else: + raise NotImplementedError + + if op.parameters["recurrent_activation"] == "hard_sigmoid": + recurrent_activation_function = "((x) < -2.5 ? 0.0 : ((x) > +2.5 ? 1.0 : ((x) * 0.2 + 0.5)))" + elif op.parameters["recurrent_activation"] == "sigmoid": + recurrent_activation_function = "(tanh(0.5f * (x)) * 0.5f + 0.5f)" + else: + raise NotImplementedError + + source = generate_template_general(use_initial_c, use_initial_h, return_sequences, + activation_function, recurrent_activation_function) + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(1, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/max_pooling_2d.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/max_pooling_2d.py index 0c6bf2e1a..76e8d273d 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/max_pooling_2d.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/max_pooling_2d.py @@ -1,31 +1,36 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.axis import Axis from webdnn.graph.operators.max_pooling_2d import MaxPooling2D from webdnn.graph.order import OrderNHWC template = """ -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *X = data_buffer + %%META_LOAD(max_pooling_2d_X_offset)%%; - device float *Y = data_buffer + %%META_LOAD(max_pooling_2d_Y_offset)%%; - const int N = %%META_LOAD(max_pooling_2d_N)%%; - const int H1 = %%META_LOAD(max_pooling_2d_H1)%%; - const int W1 = %%META_LOAD(max_pooling_2d_W1)%%; - const int C = %%META_LOAD(max_pooling_2d_C)%%; - const int H2 = %%META_LOAD(max_pooling_2d_H2)%%; - const int W2 = %%META_LOAD(max_pooling_2d_W2)%%; - const int K = %%META_LOAD(max_pooling_2d_K)%%; - const int S = %%META_LOAD(max_pooling_2d_S)%%; - const int P = %%META_LOAD(max_pooling_2d_P)%%; + const device float *X = %%LOAD_BUFFER(max_pooling_2d_X)%%; + device float *Y = %%LOAD_BUFFER(max_pooling_2d_Y)%%; + const int N = %%LOAD_BUFFER(max_pooling_2d_N)%%; + const int H1 = %%LOAD_BUFFER(max_pooling_2d_H1)%%; + const int W1 = %%LOAD_BUFFER(max_pooling_2d_W1)%%; + const int C = %%LOAD_BUFFER(max_pooling_2d_C)%%; + const int H2 = %%LOAD_BUFFER(max_pooling_2d_H2)%%; + const int W2 = %%LOAD_BUFFER(max_pooling_2d_W2)%%; + + const int KH = %%LOAD_BUFFER(max_pooling_2d_KH)%%; + const int KW = %%LOAD_BUFFER(max_pooling_2d_KW)%%; + const int SH = %%LOAD_BUFFER(max_pooling_2d_SH)%%; + const int SW = %%LOAD_BUFFER(max_pooling_2d_SW)%%; + const int PH = %%LOAD_BUFFER(max_pooling_2d_PH)%%; + const int PW = %%LOAD_BUFFER(max_pooling_2d_PW)%%; for (int gid = index; gid < N * H2 * W2 * C; gid += num_threads) { const int c = gid % C; @@ -34,12 +39,12 @@ const int n = gid / C / W2 / H2; float v = -1e7; - for (int kh = 0; kh < K; kh++) { - const int h1 = h2 * S - P + kh; + for (int kh = 0; kh < KH; kh++) { + const int h1 = h2 * SH - PH + kh; if (h1 < 0 || h1 >= H1) continue; - for (int kw = 0; kw < K; kw++) { - const int w1 = w2 * S - P + kw; + for (int kw = 0; kw < KW; kw++) { + const int w1 = w2 * SW - PW + kw; if (w1 < 0 || w1 >= W1) continue; v = v > X[((n * H1 + h1) * W1 + w1) * C + c] ? v : X[((n * H1 + h1) * W1 + w1) * C + c]; @@ -53,41 +58,44 @@ def max_pooling_2d(op: MaxPooling2D, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.order == OrderNHWC assert y.variable.order == OrderNHWC - meta_injector = MetaInjector() - meta_injector.register({ - "max_pooling_2d_X_offset": x.offset, - "max_pooling_2d_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "max_pooling_2d_X": x, + "max_pooling_2d_Y": y, "max_pooling_2d_N": x.variable.shape_dict[Axis.N], "max_pooling_2d_H1": x.variable.shape_dict[Axis.H], "max_pooling_2d_W1": x.variable.shape_dict[Axis.W], "max_pooling_2d_C": x.variable.shape_dict[Axis.C], "max_pooling_2d_H2": y.variable.shape_dict[Axis.H], "max_pooling_2d_W2": y.variable.shape_dict[Axis.W], - "max_pooling_2d_K": op.parameters["ksize"][0], - "max_pooling_2d_S": op.parameters["stride"][0], - "max_pooling_2d_P": op.parameters["padding"][0], + "max_pooling_2d_KH": op.parameters["ksize"][0], + "max_pooling_2d_KW": op.parameters["ksize"][1], + "max_pooling_2d_SH": op.parameters["stride"][0], + "max_pooling_2d_SW": op.parameters["stride"][1], + "max_pooling_2d_PH": op.parameters["padding"][0], + "max_pooling_2d_PW": op.parameters["padding"][1], }) name_injector = KernelNameInjector(op) source = template - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/reinterpret_axis.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/reinterpret_axis.py new file mode 100644 index 000000000..99c6c92f2 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/reinterpret_axis.py @@ -0,0 +1,59 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.graph.operators.reinterpret_axis import ReinterpretAxis +from webdnn.util.misc import mul + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *x = %%LOAD_BUFFER(reinterpret_axis_x)%%; + device float *y = %%LOAD_BUFFER(reinterpret_axis_y)%%; + + const int N = %%LOAD_BUFFER(reinterpret_axis_N)%%; + + for (int gid = index; gid < N; gid += num_threads) { + y[gid] = x[gid]; + } +} +""" + + +def reinterpret_axis(op: ReinterpretAxis, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.order == op.parameters["in_order"] + assert y.variable.order == op.parameters["out_order"] + + buffer_injector = BufferInjector() + buffer_injector.register({ + "reinterpret_axis_x": x, + "reinterpret_axis_y": y, + "reinterpret_axis_N": y.variable.size, + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(1024, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/relu.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/relu.py index fb7f2372d..47bee0f0e 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/relu.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/relu.py @@ -1,22 +1,23 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.operators.relu import Relu template = """ -kernel void %%FUNC_NAME%%(const device float *param_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *X = data_buffer + %%META_LOAD(relu_X_offset)%%; - device float *Y = data_buffer + %%META_LOAD(relu_Y_offset)%%; + const device float *X = %%LOAD_BUFFER(relu_X)%%; + device float *Y = %%LOAD_BUFFER(relu_Y)%%; - const int N = %%META_LOAD(relu_N)%%; + const int N = %%LOAD_BUFFER(relu_N)%%; for (int gid = index; gid < N; gid += num_threads) { float result = X[gid]; @@ -29,32 +30,32 @@ def relu(op: Relu, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.shape == y.variable.shape - meta_injector = MetaInjector() - meta_injector.register({ - "relu_X_offset": x.offset, - "relu_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "relu_X": x, + "relu_Y": y, "relu_N": y.variable.size }) - name_injector = KernelNameInjector("relu") + name_injector = KernelNameInjector(op) source = template - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/reshape.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/reshape.py new file mode 100644 index 000000000..8d88b836f --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/reshape.py @@ -0,0 +1,60 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.graph.operators.reshape import Reshape +from webdnn.util.misc import mul + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *x = %%LOAD_BUFFER(reshape_x)%%; + device float *y = %%LOAD_BUFFER(reshape_y)%%; + + const int N = %%LOAD_BUFFER(reshape_N)%%; + + for (int gid = index; gid < N; gid += num_threads) { + y[gid] = x[gid]; + } +} +""" + + +def reshape(op: Reshape, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.order == op.parameters["in_order"] + assert y.variable.order == op.parameters["out_order"] + assert y.variable.size == mul(op.parameters["out_shape"]) + + buffer_injector = BufferInjector() + buffer_injector.register({ + "reshape_x": x, + "reshape_y": y, + "reshape_N": y.variable.size, + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(1024, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/scalar_affine.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/scalar_affine.py index d7603bb7c..ca4ac76a2 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/scalar_affine.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/scalar_affine.py @@ -1,28 +1,30 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.operators.scalar_affine import ScalarAffine template = """ -kernel void %%FUNC_NAME%%(const device float *param_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *X = data_buffer + %%META_LOAD(affine_transform_X_offset)%%; - device float *Y = data_buffer + %%META_LOAD(affine_transform_Y_offset)%%; + const device float *X = %%LOAD_BUFFER(affine_transform_X)%%; + device float *Y = %%LOAD_BUFFER(affine_transform_Y)%%; - const float scale = *((const device float *)(& %%META_LOAD(affine_transform_scale)%%)); - const float bias = *((const device float *)(& %%META_LOAD(affine_transform_bias)%%)); - const int N = %%META_LOAD(affine_transform_N)%%; + const float scale = *((const device float *)(& %%LOAD_BUFFER(affine_transform_scale)%%)); + const float bias = *((const device float *)(& %%LOAD_BUFFER(affine_transform_bias)%%)); + const int N = %%LOAD_BUFFER(affine_transform_N)%%; for (int gid = index; gid < N; gid += num_threads) { float result = X[gid]; result = result * scale + bias; + Y[gid] = result; } } @@ -30,16 +32,15 @@ def scalar_affine(op: ScalarAffine, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.shape == y.variable.shape - meta_injector = MetaInjector() - meta_injector.register({ - "affine_transform_X_offset": x.offset, - "affine_transform_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "affine_transform_X": x, + "affine_transform_Y": y, "affine_transform_N": y.variable.size, "affine_transform_scale": float(op.scale), "affine_transform_bias": float(op.bias) @@ -48,15 +49,16 @@ def scalar_affine(op: ScalarAffine, name_injector = KernelNameInjector(op) source = template - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/sgemm.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/sgemm.py index bab0540d8..1102295fb 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/sgemm.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/sgemm.py @@ -1,18 +1,18 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.inline_injector import InlineInjector -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.inline_injector import InlineInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector from webdnn.backend.webgpu.kernel import Kernel, GPUSize from webdnn.backend.webgpu.operators.sgemm import Sgemm def generate_template_64(transpose_A, transpose_B, M, N, K, has_inline, with_bias): return (""" -kernel void %%FUNC_NAME%%(const device float *weight_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], ushort index[[thread_index_in_threadgroup]], ushort2 group_position[[threadgroup_position_in_grid]]) { @@ -44,18 +44,18 @@ def generate_template_64(transpose_A, transpose_B, M, N, K, has_inline, with_bia #if K_DIVIDABLE_BY_8 && M_DIVIDABLE_BY_64 && N_DIVIDABLE_BY_64 && !TRANSPOSE_A && TRANSPOSE_B && OPTIMIZE const device float4 *load_target4 = (index & 32) - ? (const device float4 *)(weight_buffer + %%META_LOAD(sgemm_B_offset)%%) - : (const device float4 *)(data_buffer + %%META_LOAD(sgemm_A_offset)%%); + ? (const device float4 *)(%%LOAD_BUFFER(sgemm_B)%%) + : (const device float4 *)(%%LOAD_BUFFER(sgemm_A)%%); #else const device float *load_target = (index & 32) - ? (weight_buffer + %%META_LOAD(sgemm_B_offset)%%) - : (data_buffer + %%META_LOAD(sgemm_A_offset)%%); + ? (%%LOAD_BUFFER(sgemm_B)%%) + : (%%LOAD_BUFFER(sgemm_A)%%); #endif - const int M = %%META_LOAD(sgemm_M)%%; - const int N = %%META_LOAD(sgemm_N)%%; + const int M = %%LOAD_BUFFER(sgemm_M)%%; + const int N = %%LOAD_BUFFER(sgemm_N)%%; - const int K = %%META_LOAD(sgemm_K)%%; + const int K = %%LOAD_BUFFER(sgemm_K)%%; threadgroup float4 shared4[32 * 8 * 2]; @@ -311,12 +311,12 @@ def generate_template_64(transpose_A, transpose_B, M, N, K, has_inline, with_bia #if OPTIMIZE && N_DIVIDABLE_BY_64 #if WITH_BIAS float4 b[2]; - const device float4 *bias4 = (const device float4 *)(weight_buffer + %%META_LOAD(sgemm_b_offset)%%); + const device float4 *bias4 = (const device float4 *)(%%LOAD_BUFFER(sgemm_b)%%); b[0] = bias4[group_position.y * 16 + n_offset * 2 + 0]; b[1] = bias4[group_position.y * 16 + n_offset * 2 + 1]; #endif - device float4 *C4 = (device float4 *)(data_buffer + %%META_LOAD(sgemm_C_offset)%%); + device float4 *C4 = (device float4 *)(%%LOAD_BUFFER(sgemm_C)%%); const int N4 = N >> 2; int m = group_position.x * 64 + m_offset * 8; for (int m_sub = 0; m_sub < 8; m_sub++) @@ -353,7 +353,7 @@ def generate_template_64(transpose_A, transpose_B, M, N, K, has_inline, with_bia } #else #if WITH_BIAS - const device float *bias = weight_buffer + %%META_LOAD(sgemm_b_offset)%%; + const device float *bias = %%LOAD_BUFFER(sgemm_b)%%; float b[8]; for (int n_sub = 0; n_sub < 8; n_sub++) { @@ -363,7 +363,7 @@ def generate_template_64(transpose_A, transpose_B, M, N, K, has_inline, with_bia } #endif - device float *C = data_buffer + %%META_LOAD(sgemm_C_offset)%%; + device float *C = %%LOAD_BUFFER(sgemm_C)%%; int m = group_position.x * 64 + m_offset * 8; for (int m_sub = 0; m_sub < 8; m_sub++) { @@ -421,20 +421,19 @@ def generate_template_64(transpose_A, transpose_B, M, N, K, has_inline, with_bia def sgemm(op: Sgemm, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - A = variables_layout[op.inputs["A"]] if op.inputs["A"] in variables_layout else constants_layout[op.inputs["A"]] - B = variables_layout[op.inputs["B"]] if op.inputs["B"] in variables_layout else constants_layout[op.inputs["B"]] - C = variables_layout[op.outputs["C"]] + memory_layout: MemoryLayout) -> List[Kernel]: + A = memory_layout[op.inputs["A"]] + B = memory_layout[op.inputs["B"]] + C = memory_layout[op.outputs["C"]] with_bias = "b" in op.inputs - meta_injector = MetaInjector() - meta_injector.register({ - "sgemm_A_offset": A.offset, - "sgemm_B_offset": B.offset, - "sgemm_C_offset": C.offset, - "sgemm_b_offset": constants_layout[op.inputs["b"]].offset if with_bias else 0, + buffer_injector = BufferInjector() + buffer_injector.register({ + "sgemm_A": A, + "sgemm_B": B, + "sgemm_C": C, + "sgemm_b": memory_layout[op.inputs["b"]] if with_bias else 0, "sgemm_M": op.M, "sgemm_N": op.N, "sgemm_K": op.K @@ -447,7 +446,7 @@ def sgemm(op: Sgemm, # In default convolution, transpose_A == transpose_B == True. # The order of output matrix C is C-order. source = generate_template_64(op.transpose_A, op.transpose_B, op.M, op.N, op.K, inline_injector.has_inline, with_bias) - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = inline_injector.inject(source) source = name_injector.inject(source) @@ -456,7 +455,8 @@ def sgemm(op: Sgemm, name_injector.name, GPUSize((op.M + 64 - 1) // 64, (op.N + 64 - 1) // 64, 1), GPUSize(64, 1, 1), - meta_injector.buffer + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/sigmoid.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/sigmoid.py new file mode 100644 index 000000000..fab17d75e --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/sigmoid.py @@ -0,0 +1,61 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.operators.sigmoid import Sigmoid + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%%[[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *X = %%LOAD_BUFFER(sigmoid_X)%%; + device float *Y = %%LOAD_BUFFER(sigmoid_Y)%%; + + const int N = %%LOAD_BUFFER(sigmoid_N)%%; + + for (int gid = index; gid < N; gid += num_threads) { + float result = X[gid]; + result = tanh(0.5f * result) * 0.5f + 0.5f; + + Y[gid] = result; + } +} +""" + + +def sigmoid(op: Sigmoid, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "sigmoid_X": x, + "sigmoid_Y": y, + "sigmoid_N": y.variable.size + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/softmax.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/softmax.py new file mode 100644 index 000000000..348a7afa7 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/softmax.py @@ -0,0 +1,100 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.operators.softmax import Softmax +from webdnn.util.misc import mul + + +def softmax(op: Softmax, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + if x.variable.order == y.variable.order: + return softmax_same_order(op, memory_layout) + + else: + raise NotImplementedError() + + +template_same_order = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *X = %%LOAD_BUFFER(softmax_X)%%; + device float *Y = %%LOAD_BUFFER(softmax_Y)%%; + const int D1 = %%LOAD_BUFFER(softmax_D1)%%; + const int D2 = %%LOAD_BUFFER(softmax_D2)%%; + const int D3 = %%LOAD_BUFFER(softmax_D3)%%; + + for (int gid = index; gid < D1 * D3; gid += num_threads) { + const int d3 = gid % D3; + const int d1 = gid / D3; + + float set_max = 0.0f; + for (int d2 = 0; d2 < D2; d2++) { + float val = X[(d1 * D2 + d2) * D3 + d3]; + if (val > set_max) { + set_max = val; + } + } + + float sum_exp = 0.0f; + for (int d2 = 0; d2 < D2; d2++) { + float val = X[(d1 * D2 + d2) * D3 + d3]; + float exp_x = exp(val - set_max); + sum_exp += exp_x; + Y[(d1 * D2 + d2) * D3 + d3] = exp_x; + } + + for (int d2 = 0; d2 < D2; d2++) { + Y[(d1 * D2 + d2) * D3 + d3] /= sum_exp; + } + } +} +""" + + +def softmax_same_order(op: Softmax, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + target_axis = op.parameters["axis"] + target_axis_index = x.variable.order.axes_dict[target_axis] + D1 = mul(x.variable.shape[:target_axis_index]) + D2 = x.variable.shape[target_axis_index] + D3 = mul(x.variable.shape[target_axis_index + 1:]) + + buffer_injector = BufferInjector() + buffer_injector.register({ + "softmax_X": x, + "softmax_Y": y, + "softmax_D1": D1, + "softmax_D2": D2, + "softmax_D3": D3 + }) + + name_injector = KernelNameInjector(op) + + source = template_same_order + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/softplus.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/softplus.py new file mode 100644 index 000000000..767f1ebd1 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/softplus.py @@ -0,0 +1,64 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.operators.softplus import Softplus + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%%[[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *X = %%LOAD_BUFFER(softplus_X)%%; + device float *Y = %%LOAD_BUFFER(softplus_Y)%%; + + const int N = %%LOAD_BUFFER(softplus_N)%%; + const float beta = *((const device float *)(& %%LOAD_BUFFER(softplus_beta)%%)); + const float beta_inv = 1.0 / beta; + + for (int gid = index; gid < N; gid += num_threads) { + float result = X[gid]; + result = log(1.0f + exp(beta * result)) * beta_inv; + + Y[gid] = result; + } +} +""" + + +def softplus(op: Softplus, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "softplus_X": x, + "softplus_Y": y, + "softplus_N": y.variable.size, + "softplus_beta": op.parameters["beta"] + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/softsign.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/softsign.py new file mode 100644 index 000000000..3687f8dc0 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/softsign.py @@ -0,0 +1,61 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.operators.softsign import Softsign + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%%[[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *X = %%LOAD_BUFFER(softsign_X)%%; + device float *Y = %%LOAD_BUFFER(softsign_Y)%%; + + const int N = %%LOAD_BUFFER(softsign_N)%%; + + for (int gid = index; gid < N; gid += num_threads) { + float result = X[gid]; + result = result / (fabs(result) + 1.0f); + + Y[gid] = result; + } +} +""" + + +def softsign(op: Softsign, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.shape == y.variable.shape + + buffer_injector = BufferInjector() + buffer_injector.register({ + "softsign_X": x, + "softsign_Y": y, + "softsign_N": y.variable.size + }) + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/tanh.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/tanh.py index 479d8ea5f..b017a681a 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/kernels/tanh.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/tanh.py @@ -1,22 +1,23 @@ from typing import List -from webdnn.backend.webgpu.allocator import MemoryLayout -from webdnn.backend.webgpu.injectors.kernel_name_injector import KernelNameInjector -from webdnn.backend.webgpu.injectors.meta_injector import MetaInjector +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector from webdnn.backend.webgpu.kernel import GPUSize, Kernel +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP from webdnn.graph.operators.tanh import Tanh template = """ -kernel void %%FUNC_NAME%%(const device float *param_buffer[[buffer(0)]], - device float *data_buffer[[buffer(1)]], - const device int * %%META_NAME%% [[buffer(2)]], +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], uint index[[thread_position_in_grid]], uint num_threads[[threads_per_grid]]) { - const device float *X = data_buffer + %%META_LOAD(tanh_X_offset)%%; - device float *Y = data_buffer + %%META_LOAD(tanh_Y_offset)%%; + const device float *X = %%LOAD_BUFFER(tanh_X)%%; + device float *Y = %%LOAD_BUFFER(tanh_Y)%%; - const int N = %%META_LOAD(tanh_N)%%; + const int N = %%LOAD_BUFFER(tanh_N)%%; for (int gid = index; gid < N; gid += num_threads) { Y[gid] = tanh(X[gid]); @@ -26,33 +27,33 @@ def tanh(op: Tanh, - constants_layout: MemoryLayout, - variables_layout: MemoryLayout) -> List[Kernel]: - x = variables_layout[op.inputs["x"]] - y = variables_layout[op.outputs["y"]] + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] assert x.variable.shape == y.variable.shape assert x.variable.order == y.variable.order - meta_injector = MetaInjector() - meta_injector.register({ - "tanh_X_offset": x.offset, - "tanh_Y_offset": y.offset, + buffer_injector = BufferInjector() + buffer_injector.register({ + "tanh_X": x, + "tanh_Y": y, "tanh_N": y.variable.size }) name_injector = KernelNameInjector(op) source = template - source = meta_injector.inject(source) + source = buffer_injector.inject(source) source = name_injector.inject(source) kernel = Kernel( {name_injector.name: source}, name_injector.name, GPUSize(8, 1, 1), - GPUSize(1024, 1, 1), - meta_injector.buffer + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list ) return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/kernels/zero_padding_1d.py b/src/graph_transpiler/webdnn/backend/webgpu/kernels/zero_padding_1d.py new file mode 100644 index 000000000..08092c715 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/kernels/zero_padding_1d.py @@ -0,0 +1,79 @@ +from typing import List + +from webdnn.backend.code_generator.allocator import MemoryLayout +from webdnn.backend.code_generator.injectors.buffer_injector import BufferInjector +from webdnn.backend.code_generator.injectors.kernel_name_injector import KernelNameInjector +from webdnn.backend.webgpu.kernel import Kernel, GPUSize +from webdnn.backend.webgpu.preset_placeholders import MAX_THREADS_PER_THREADGROUP +from webdnn.graph.axis import Axis +from webdnn.graph.operators.zero_padding_1d import ZeroPadding1D +from webdnn.graph.order import OrderNTC + +template = """ +kernel void %%FUNC_NAME%%(device float * %%STATIC_BUFFER%%[[buffer(0)]], + device float * %%DYNAMIC_BUFFER%%[[buffer(1)]], + const device int * %%META_BUFFER%% [[buffer(2)]], + uint index[[thread_position_in_grid]], + uint num_threads[[threads_per_grid]]) +{ + const device float *X = %%LOAD_BUFFER(zero_padding_1d_X)%%; + device float *Y = %%LOAD_BUFFER(zero_padding_1d_Y)%%; + const int N = %%LOAD_BUFFER(zero_padding_1d_N)%%; + const int T1 = %%LOAD_BUFFER(zero_padding_1d_T1)%%; + const int C = %%LOAD_BUFFER(zero_padding_1d_C)%%; + const int T2 = %%LOAD_BUFFER(zero_padding_1d_T2)%%; + const int Pad1L = %%LOAD_BUFFER(zero_padding_1d_Pad1L)%%; + + for (int gid = index; gid < N * T2 * C; gid += num_threads) { + const int c = gid % C; + const int t2 = gid / C % T2; + const int n = gid / C / T2; + + const int t1 = t2 - Pad1L; + float v = 0.0F; + if ((t1 >= 0) && (t1 < T1)) { + v = X[(n * T1 + t1) * C + c]; + } + + Y[gid] = v; + } +} +""" + + +def zero_padding_1d(op: ZeroPadding1D, + memory_layout: MemoryLayout) -> List[Kernel]: + x = memory_layout[op.inputs["x"]] + y = memory_layout[op.outputs["y"]] + + assert x.variable.order == OrderNTC + assert y.variable.order == OrderNTC + + buffer_injector = BufferInjector() + buffer_injector.register({ + "zero_padding_1d_X": x, + "zero_padding_1d_Y": y, + "zero_padding_1d_N": x.variable.shape_dict[Axis.N], + "zero_padding_1d_T1": x.variable.shape_dict[Axis.T], + "zero_padding_1d_C": x.variable.shape_dict[Axis.C], + "zero_padding_1d_T2": y.variable.shape_dict[Axis.T], + "zero_padding_1d_Pad1L": op.parameters["padding"][0], + }) + # "zero_padding_1d_Pad1H": op.parameters["padding"][1] # unused in kernel + + name_injector = KernelNameInjector(op) + + source = template + source = buffer_injector.inject(source) + source = name_injector.inject(source) + + kernel = Kernel( + {name_injector.name: source}, + name_injector.name, + GPUSize(8, 1, 1), + GPUSize(MAX_THREADS_PER_THREADGROUP, 1, 1), + buffer_injector.buffer, + buffer_injector.unresolved_value_list + ) + + return [kernel] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/libs.metal b/src/graph_transpiler/webdnn/backend/webgpu/libs.metal new file mode 100644 index 000000000..604c24838 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/libs.metal @@ -0,0 +1,193 @@ +#include +using namespace metal; + +void sgemm(bool, bool, const int, const int, const int, const device float *, const device float *, device float *, threadgroup float4 *, ushort, ushort2); + +void sgemm( + bool TRANSPOSE_A, // "true" means A is row-major(C-style), "false" means A is column-major(Fortran-style) + bool TRANSPOSE_B, // "true" means B is row-major(C-style), "false" means B is column-major(Fortran-style) + const int M, // number of rows of A and C + const int N, // number of columns of B and C + const int K, // number of columns of A and rows of B + const device float *A, + const device float *B, + device float *C, + threadgroup float4 *shared4, // threadgroup float4 array, whose length must be 512 + ushort thread_index, // number of thread per threadgroup must be 64 + ushort2 group_position // threadgroup size must be {(M+63)//64, (N+63)//64, 1} +) +{ + const device float *load_target = (thread_index & 32) ? B : A; + + const int A_STRIDE_K = TRANSPOSE_A ? 1 : M; + const int A_STRIDE_M = TRANSPOSE_A ? K : 1; + const int B_STRIDE_K = TRANSPOSE_B ? N : 1; + const int B_STRIDE_N = TRANSPOSE_B ? 1 : K; + + const int stride_k = (thread_index & 32) ? B_STRIDE_K : A_STRIDE_K; + const int stride_mn = (thread_index & 32) ? B_STRIDE_N : A_STRIDE_M; + + const int m_offset = thread_index & 7; + const int n_offset = thread_index >> 3; + + const int mn_load_offset = ((thread_index & 32) ? group_position.y : group_position.x) * 64 + (thread_index & 15) * 4; + const int k_load_offset = ((thread_index & 16) ? 1 : 0); + + int track0 = mn_load_offset * stride_mn + (k_load_offset + 0) * stride_k; + int track2 = track0 + 2 * stride_k; + int track4 = track0 + 4 * stride_k; + int track6 = track0 + 6 * stride_k; + + const int max_MN = (thread_index & 32) ? N : M; + + int shared_offset4 = ((thread_index & 32) ? 16 : 0) + k_load_offset * 32 + (thread_index & 15); + int read_A_offset4 = m_offset * 2; + int read_B_offset4 = n_offset * 2 + 16; + + float4 result[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int k = 0; + + while (k < K) + { + { + shared4[shared_offset4 + 32 * 0] = float4( + (k + k_load_offset >= K || mn_load_offset + 0 >= max_MN) ? 0 : load_target[track0 + stride_mn * 0], + (k + k_load_offset >= K || mn_load_offset + 1 >= max_MN) ? 0 : load_target[track0 + stride_mn * 1], + (k + k_load_offset >= K || mn_load_offset + 2 >= max_MN) ? 0 : load_target[track0 + stride_mn * 2], + (k + k_load_offset >= K || mn_load_offset + 3 >= max_MN) ? 0 : load_target[track0 + stride_mn * 3] + ); + k += 2; + + shared4[shared_offset4 + 32 * 2] = float4( + (k + k_load_offset >= K || mn_load_offset + 0 >= max_MN) ? 0 : load_target[track2 + stride_mn * 0], + (k + k_load_offset >= K || mn_load_offset + 1 >= max_MN) ? 0 : load_target[track2 + stride_mn * 1], + (k + k_load_offset >= K || mn_load_offset + 2 >= max_MN) ? 0 : load_target[track2 + stride_mn * 2], + (k + k_load_offset >= K || mn_load_offset + 3 >= max_MN) ? 0 : load_target[track2 + stride_mn * 3] + ); + k += 2; + + shared4[shared_offset4 + 32 * 4] = float4( + (k + k_load_offset >= K || mn_load_offset + 0 >= max_MN) ? 0 : load_target[track4 + stride_mn * 0], + (k + k_load_offset >= K || mn_load_offset + 1 >= max_MN) ? 0 : load_target[track4 + stride_mn * 1], + (k + k_load_offset >= K || mn_load_offset + 2 >= max_MN) ? 0 : load_target[track4 + stride_mn * 2], + (k + k_load_offset >= K || mn_load_offset + 3 >= max_MN) ? 0 : load_target[track4 + stride_mn * 3] + ); + k += 2; + + shared4[shared_offset4 + 32 * 6] = float4( + (k + k_load_offset >= K || mn_load_offset + 0 >= max_MN) ? 0 : load_target[track6 + stride_mn * 0], + (k + k_load_offset >= K || mn_load_offset + 1 >= max_MN) ? 0 : load_target[track6 + stride_mn * 1], + (k + k_load_offset >= K || mn_load_offset + 2 >= max_MN) ? 0 : load_target[track6 + stride_mn * 2], + (k + k_load_offset >= K || mn_load_offset + 3 >= max_MN) ? 0 : load_target[track6 + stride_mn * 3] + ); + k += 2; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + { + for (int k_sub = 0; k_sub < 8; k_sub++) + { + float4 a00 = shared4[32 * k_sub + read_A_offset4 + 0]; + float4 a01 = shared4[32 * k_sub + read_A_offset4 + 1]; + float4 b00 = shared4[32 * k_sub + read_B_offset4 + 0]; + float4 b01 = shared4[32 * k_sub + read_B_offset4 + 1]; + + result[4][0] += b00[0] * a00[2]; + result[4][1] += b00[1] * a00[2]; + result[0][1] += b00[1] * a00[0]; + result[0][0] += b00[0] * a00[0]; + result[6][0] += b00[0] * a00[3]; + result[6][1] += b00[1] * a00[3]; + result[2][1] += b00[1] * a00[1]; + result[2][0] += b00[0] * a00[1]; + result[12][0] += b00[0] * a01[2]; + result[12][1] += b00[1] * a01[2]; + result[8][1] += b00[1] * a01[0]; + result[8][0] += b00[0] * a01[0]; + result[14][0] += b00[0] * a01[3]; + result[14][1] += b00[1] * a01[3]; + result[10][1] += b00[1] * a01[1]; + result[10][0] += b00[0] * a01[1]; + + result[14][2] += b00[2] * a01[3]; + result[14][3] += b00[3] * a01[3]; + result[10][3] += b00[3] * a01[1]; + result[10][2] += b00[2] * a01[1]; + result[12][2] += b00[2] * a01[2]; + result[12][3] += b00[3] * a01[2]; + result[8][3] += b00[3] * a01[0]; + result[8][2] += b00[2] * a01[0]; + result[6][2] += b00[2] * a00[3]; + result[6][3] += b00[3] * a00[3]; + result[2][3] += b00[3] * a00[1]; + result[2][2] += b00[2] * a00[1]; + result[4][2] += b00[2] * a00[2]; + result[4][3] += b00[3] * a00[2]; + result[0][3] += b00[3] * a00[0]; + result[0][2] += b00[2] * a00[0]; + + result[5][0] += b01[0] * a00[2]; + result[5][1] += b01[1] * a00[2]; + result[1][1] += b01[1] * a00[0]; + result[1][0] += b01[0] * a00[0]; + result[7][0] += b01[0] * a00[3]; + result[7][1] += b01[1] * a00[3]; + result[3][1] += b01[1] * a00[1]; + result[3][0] += b01[0] * a00[1]; + result[13][0] += b01[0] * a01[2]; + result[13][1] += b01[1] * a01[2]; + result[9][1] += b01[1] * a01[0]; + result[9][0] += b01[0] * a01[0]; + result[15][0] += b01[0] * a01[3]; + result[15][1] += b01[1] * a01[3]; + result[11][1] += b01[1] * a01[1]; + result[11][0] += b01[0] * a01[1]; + + result[15][2] += b01[2] * a01[3]; + result[15][3] += b01[3] * a01[3]; + result[11][3] += b01[3] * a01[1]; + result[11][2] += b01[2] * a01[1]; + result[13][2] += b01[2] * a01[2]; + result[13][3] += b01[3] * a01[2]; + result[9][3] += b01[3] * a01[0]; + result[9][2] += b01[2] * a01[0]; + result[7][2] += b01[2] * a00[3]; + result[7][3] += b01[3] * a00[3]; + result[3][3] += b01[3] * a00[1]; + result[3][2] += b01[2] * a00[1]; + result[5][2] += b01[2] * a00[2]; + result[5][3] += b01[3] * a00[2]; + result[1][3] += b01[3] * a00[0]; + result[1][2] += b01[2] * a00[0]; + } + } + + shared_offset4 ^= 32 * 8; + read_A_offset4 ^= 32 * 8; + read_B_offset4 ^= 32 * 8; + track0 += stride_k * 8; + track2 += stride_k * 8; + track4 += stride_k * 8; + track6 += stride_k * 8; + } + + { + int m = group_position.x * 64 + m_offset * 8; + for (int m_sub = 0; m_sub < 8; m_sub++) + { + int n = group_position.y * 64 + n_offset * 8; + + for (int n_sub1 = 0; n_sub1 < 2; n_sub1++) + { + for (int n_sub2 = 0; n_sub2 < 4; n_sub2++) + { + (m < M && n < N) ? (C[m * N + n] = result[m_sub * 2 + n_sub1][n_sub2]) : 0; + n++; + } + } + + m++; + } + } +} diff --git a/src/graph_transpiler/webdnn/backend/webgpu/operators/im2col.py b/src/graph_transpiler/webdnn/backend/webgpu/operators/im2col.py index ea074ce03..36f9fedbd 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/operators/im2col.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/operators/im2col.py @@ -8,16 +8,18 @@ class Im2Col(Operator): - def __init__(self, name: Optional[str], ksize: IntOrTuple, stride: IntOrTuple, padding: IntOrTuple): + def __init__(self, name: Optional[str], ksize: IntOrTuple, stride: IntOrTuple, padding: IntOrTuple, + dilation_rate: IntOrTuple): super().__init__(name) self.parameters["ksize"] = to_tuple(ksize) self.parameters["stride"] = to_tuple(stride) self.parameters["padding"] = to_tuple(padding) + self.parameters["dilation_rate"] = to_tuple(dilation_rate) def __call__(self, im: Variable): N = im.shape_dict[Axis.N] - H2 = (im.shape_dict[Axis.H] + 2 * self.PH - self.KH) // self.SH + 1 - W2 = (im.shape_dict[Axis.W] + 2 * self.PW - self.KW) // self.SW + 1 + H2 = (im.shape_dict[Axis.H] + 2 * self.PH - self.WH) // self.SH + 1 + W2 = (im.shape_dict[Axis.W] + 2 * self.PW - self.WW) // self.SW + 1 C1 = im.shape_dict[Axis.C] col = Variable([N, H2, W2, self.KH * self.KW * C1], OrderNHWC) @@ -39,6 +41,10 @@ def stride(self) -> Tuple[int, int]: def padding(self) -> Tuple[int, int]: return self.parameters["padding"] + @property + def dilation_rate(self) -> Tuple[int, int]: + return self.parameters["dilation_rate"] + @property def KH(self) -> int: return self.parameters["ksize"][0] @@ -62,3 +68,29 @@ def PH(self) -> int: @property def PW(self) -> int: return self.parameters["padding"][1] + + @property + def DH(self) -> int: + return self.parameters["dilation_rate"][0] + + @property + def DW(self) -> int: + return self.parameters["dilation_rate"][1] + + @property + def WH(self) -> int: + """ + Input window height considering dilation. + Returns: + + """ + return self.DH * (self.KH - 1) + 1 + + @property + def WW(self) -> int: + """ + Input window width considering dilation. + Returns: + + """ + return self.DW * (self.KW - 1) + 1 diff --git a/src/graph_transpiler/webdnn/backend/webgpu/operators/sgemm.py b/src/graph_transpiler/webdnn/backend/webgpu/operators/sgemm.py index 8f353c1d2..dcdd33dc2 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/operators/sgemm.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/operators/sgemm.py @@ -1,15 +1,17 @@ -from typing import Optional, Iterable +from typing import Optional, Iterable, Union import numpy as np from webdnn.graph.operator import Operator from webdnn.graph.order import Order +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable +from webdnn.util.misc import mul class Sgemm(Operator): - def __init__(self, name: Optional[str], M: int, N: int, K: int, out_shape: Iterable[int], out_order: Order, - transpose_A: bool, transpose_B: bool): + def __init__(self, name: Optional[str], M: Union[int, Placeholder], N: Union[int, Placeholder], K: Union[int, Placeholder], + out_shape: Iterable[Union[int, Placeholder]], out_order: Order, transpose_A: bool, transpose_B: bool): super().__init__(name) # NOTE: out_shapeをIterableではなくCollectionにすればこれは解決する @@ -18,7 +20,8 @@ def __init__(self, name: Optional[str], M: int, N: int, K: int, out_shape: Itera # # noinspection PyTypeChecker assert len(out_shape) == out_order.ndim - assert np.product(out_shape) == M * N + if Placeholder.check_resolved(mul(out_shape)) and Placeholder.check_resolved(M * N): + assert mul(out_shape) == M * N self.parameters["M"] = M self.parameters["N"] = N @@ -29,8 +32,11 @@ def __init__(self, name: Optional[str], M: int, N: int, K: int, out_shape: Itera self.parameters["transpose_B"] = transpose_B def __call__(self, A: Variable, B: Variable): - assert A.size == self.M * self.K - assert B.size == self.N * self.K + if Placeholder.check_resolved(A.size) and Placeholder.check_resolved(self.M * self.K): + assert A.size == self.M * self.K + + if Placeholder.check_resolved(B.size) and Placeholder.check_resolved(self.N * self.K): + assert B.size == self.N * self.K self.append_input("A", A) self.append_input("B", B) @@ -44,21 +50,21 @@ def __call__(self, A: Variable, B: Variable): return C, @property - def M(self) -> int: - return int(self.parameters["M"]) + def M(self) -> Union[int, Placeholder]: + return self.parameters["M"] @property - def N(self) -> int: - return int(self.parameters["N"]) + def N(self) -> Union[int, Placeholder]: + return self.parameters["N"] @property - def K(self) -> int: - return int(self.parameters["K"]) + def K(self) -> Union[int, Placeholder]: + return self.parameters["K"] @property def transpose_A(self) -> bool: - return bool(self.parameters["transpose_A"]) + return self.parameters["transpose_A"] @property def transpose_B(self) -> bool: - return bool(self.parameters["transpose_B"]) + return self.parameters["transpose_B"] diff --git a/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/optimize_lstm.py b/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/optimize_lstm.py new file mode 100644 index 000000000..ac7dcd1c3 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/optimize_lstm.py @@ -0,0 +1,8 @@ +from webdnn.backend.webgpu.optimize_rules.sub_rules.concat_lstm_input_and_hidden import ConcatLSTMInputAndHidden +from webdnn.graph.optimize_rule import OptimizeRule + + +class OptimizeLSTM(OptimizeRule): + def __init__(self): + super(OptimizeLSTM, self).__init__() + self.register(ConcatLSTMInputAndHidden()) diff --git a/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/sub_rules/concat_lstm_input_and_hidden.py b/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/sub_rules/concat_lstm_input_and_hidden.py new file mode 100644 index 000000000..cb38ff5cc --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/sub_rules/concat_lstm_input_and_hidden.py @@ -0,0 +1,80 @@ +from typing import Tuple + +import numpy as np + +from webdnn.backend.webgpu.attributes.lstm_optimized import LSTMOptimized +from webdnn.graph import traverse +from webdnn.graph.axis import Axis +from webdnn.graph.graph import Graph +from webdnn.graph.operators.concat import Concat +from webdnn.graph.operators.lstm import LSTM +from webdnn.graph.optimize_rule import OptimizeRule +from webdnn.graph.order import OrderCN, OrderNC +from webdnn.graph.variable import Variable +from webdnn.graph.variables.constant_variable import ConstantVariable + + +class ConcatLSTMInputAndHidden(OptimizeRule): + """ + In typical LSTM, input gate signal(v_i), forget gate signal(v_f), cell update signal(v_a), and output gate signal(v_o) are calculated + as follows: + + v_i = w_i * x + w'_i * h + v_f = w_f * x + w'_f * h + v_a = w_a * x + w'_a * h + v_o = w_o * x + w'_o * h + + typical LSTM implementation combine i, f, a, o into single tensor as follows: + + v = W * x + W' * h + + This optimize rule concat W and W', and x and y + + v = W_all * XH + + Also this optimize rule append 1 additional input: + + workspace: + store the data of product of W_all and XH (=`v` in above equations) + + """ + + def optimize(self, graph: Graph) -> Tuple[Graph, bool]: + flag_changed = False + for match in traverse.search_sub_structure(graph, [LSTM]): + lstm = match[0] # type: LSTM + + if lstm.has_attribute(LSTMOptimized): + continue + + x = lstm.inputs["x"] + w_input = lstm.inputs["w_input"] + w_hidden = lstm.inputs["w_hidden"] + if isinstance(w_input, ConstantVariable) and isinstance(w_hidden, ConstantVariable): + w_all = ConstantVariable(np.vstack([w_input.data, w_hidden.data]), OrderCN) + else: + w_all, = Concat(None, axis=Axis.C)(w_input, w_hidden) # type: Variable + w_all.change_order(OrderCN) + + attr = LSTMOptimized(lstm) + + N = x.shape_dict[Axis.N] + C1 = attr.C1 + C2 = attr.C2 + + x_and_h = Variable([C1 + C2, N], OrderCN) + workspace = Variable([N, 4 * C2], OrderNC) + + w_input.change_order(OrderCN) + w_hidden.change_order(OrderCN) + + lstm.remove_input(w_input) + lstm.remove_input(w_hidden) + lstm.append_input("x_and_h", x_and_h) + lstm.append_input("workspace", workspace) + lstm.append_input("w_all", w_all) + lstm.attributes.add(attr) + + flag_changed = True + + return graph, flag_changed diff --git a/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/sub_rules/replace_convolution_by_im2col.py b/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/sub_rules/replace_convolution_by_im2col.py index f70f2ad05..0675102b7 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/sub_rules/replace_convolution_by_im2col.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/sub_rules/replace_convolution_by_im2col.py @@ -21,7 +21,7 @@ def optimize(self, graph: Graph) -> Tuple[Graph, bool]: if not isinstance(op, Convolution2D): continue - op: Convolution2D + op = op # type: Convolution2D x = op.inputs["x"] w = op.inputs["w"] @@ -34,8 +34,9 @@ def optimize(self, graph: Graph) -> Tuple[Graph, bool]: w.change_order(OrderHWCN) assert old_y.order == OrderNHWC - if op.ksize[0] > 1 or op.ksize[1] > 1 or op.stride[0] > 1 or op.stride[1] > 1 or op.padding[0] > 0 or op.padding[1] > 0: - im2col = Im2Col(None, ksize=op.ksize, stride=op.stride, padding=op.padding) + if op.WH != 1 or op.WW != 1 or op.stride != (1, 1) or op.padding != (0, 0): + im2col = Im2Col(None, ksize=op.ksize, stride=op.stride, padding=op.padding, + dilation_rate=op.dilation_rate) col, = im2col(x) col.change_order(OrderNHWC) @@ -46,10 +47,12 @@ def optimize(self, graph: Graph) -> Tuple[Graph, bool]: M=col.shape_dict[Axis.N] * col.shape_dict[Axis.H] * col.shape_dict[Axis.W], N=w.shape_dict[Axis.N], K=col.shape_dict[Axis.C], - out_shape=[col.shape_dict[Axis.N], col.shape_dict[Axis.H], col.shape_dict[Axis.W], w.shape_dict[Axis.N]], + out_shape=[col.shape_dict[Axis.N], col.shape_dict[Axis.H], col.shape_dict[Axis.W], + w.shape_dict[Axis.N]], out_order=OrderNHWC, transpose_A=True if col.order == OrderNHWC else False, transpose_B=True) + new_y, = sgemm(col, w) sgemm.replace_output(new_y, old_y) diff --git a/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/webgpu_optimize_rule.py b/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/webgpu_optimize_rule.py index b4d157a9b..82ec33953 100644 --- a/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/webgpu_optimize_rule.py +++ b/src/graph_transpiler/webdnn/backend/webgpu/optimize_rules/webgpu_optimize_rule.py @@ -3,6 +3,7 @@ from webdnn.backend.webgpu.optimize_rules.optimize_flatten import OptimizeFlatten from webdnn.backend.webgpu.optimize_rules.optimize_inline_inplace import OptimizeInlineInplace from webdnn.backend.webgpu.optimize_rules.optimize_linear import OptimizeLinear +from webdnn.backend.webgpu.optimize_rules.optimize_lstm import OptimizeLSTM from webdnn.graph.optimize_rule import OptimizeRule @@ -15,3 +16,4 @@ def __init__(self): self.register(OptimizeInlineInplace()) self.register(OptimizeFlatten()) self.register(OptimizeLinear()) + self.register(OptimizeLSTM()) diff --git a/src/graph_transpiler/webdnn/backend/webgpu/preset_placeholders.py b/src/graph_transpiler/webdnn/backend/webgpu/preset_placeholders.py new file mode 100644 index 000000000..f8d3e3d94 --- /dev/null +++ b/src/graph_transpiler/webdnn/backend/webgpu/preset_placeholders.py @@ -0,0 +1,3 @@ +from webdnn.graph.placeholder import Placeholder + +MAX_THREADS_PER_THREADGROUP = Placeholder(label="__MAX_THREADS_PER_THREADGROUP__") diff --git a/src/graph_transpiler/webdnn/backend/webgpu/tag_parser.py b/src/graph_transpiler/webdnn/backend/webgpu/tag_parser.py deleted file mode 100644 index 72db90274..000000000 --- a/src/graph_transpiler/webdnn/backend/webgpu/tag_parser.py +++ /dev/null @@ -1,36 +0,0 @@ -import re -from typing import List, Tuple, NamedTuple - - -class Tag(NamedTuple): - original: str - name: str - args: List[str] - span: Tuple[int, int] - - -reg_tag = re.compile("%%([a-zA-Z0-9_]+)(?:\((.*)\))?%%", re.MULTILINE) - - -class TagParser: - @classmethod - def parse(cls, text: str) -> List[Tag]: - pos = 0 - result = [] - while True: - ma = reg_tag.search(text, pos) - if ma is None: - break - pos = ma.end() - - original = ma.group(0) - name = ma.group(1) - args = [] if ma.group(2) is None else list(map(str.strip, ma.group(2).split(","))) - span = ma.span() - - # FIXME: This noinspection comment is not required. It's maybe a PyCharm's Bug? - # noinspection PyArgumentList - result.append(Tag(original, name, args, span)) - - result.sort(key=lambda x: x.span[0], reverse=True) - return result diff --git a/src/graph_transpiler/webdnn/encoder/constant_encoder.py b/src/graph_transpiler/webdnn/encoder/constant_encoder.py index ed46f60e2..fd10a715a 100644 --- a/src/graph_transpiler/webdnn/encoder/constant_encoder.py +++ b/src/graph_transpiler/webdnn/encoder/constant_encoder.py @@ -1,16 +1,15 @@ -import numpy as np - -from webdnn.backend.interface.memory_layout import IMemoryLayout +from webdnn.backend.code_generator.allocator import MemoryLayout class ConstantEncoder: name: str - def encode(self, constant_layout: IMemoryLayout, data: np.ndarray) -> bytes: + def encode(self, memory_layout: MemoryLayout) -> bytes: raise NotImplementedError() @classmethod def get_encoder(cls, name: str = None) -> "ConstantEncoder": + # FIXME from webdnn.encoder.constant_encoder_raw import ConstantEncoderRaw from webdnn.encoder.constant_encoder_eightbit import ConstantEncoderEightbit if name is None or name == "raw": diff --git a/src/graph_transpiler/webdnn/encoder/constant_encoder_eightbit.py b/src/graph_transpiler/webdnn/encoder/constant_encoder_eightbit.py index 4d0176fc8..0db2f00ae 100644 --- a/src/graph_transpiler/webdnn/encoder/constant_encoder_eightbit.py +++ b/src/graph_transpiler/webdnn/encoder/constant_encoder_eightbit.py @@ -5,8 +5,9 @@ import numpy as np -from webdnn.backend.interface.memory_layout import IMemoryLayout, IAllocation +from webdnn.backend.code_generator.allocator import Allocation, MemoryLayout from webdnn.encoder.constant_encoder import ConstantEncoder +from webdnn.graph.variables.constant_variable import ConstantVariable tbl_floats = [2.750000021e-06, 7.249999726e-06, 1.875000089e-05, 3.624999954e-05, 5.874999624e-05, 8.624999464e-05, 1.437500032e-04, 2.312500001e-04, 3.187500115e-04, 4.062500084e-04, 5.187499919e-04, 6.562499912e-04, @@ -56,15 +57,19 @@ class ConstantEncoderEightbit(ConstantEncoder): def __init__(self): self.name = "eightbit" - def encode(self, constant_layout: IMemoryLayout, data: np.ndarray) -> bytes: + def encode(self, memory_layout: MemoryLayout) -> bytes: all_code = b"" - for alloc in constant_layout.__dict__.values(): - single_data = data[alloc.offset:alloc.offset + alloc.size] + for alloc in memory_layout.allocations.values(): + if not isinstance(alloc.variable, ConstantVariable): + continue + + single_data = memory_layout.data[alloc.offset:alloc.offset + alloc.size] all_code += self._single_encode(single_data, alloc) return all_code - def _single_encode(self, single_data: np.ndarray, alloc: IAllocation) -> bytes: + # noinspection PyMethodMayBeStatic + def _single_encode(self, single_data: np.ndarray, alloc: Allocation) -> bytes: maxval = np.max(np.abs(single_data)) maxval = np.maximum(maxval, 1e-20) # avoid zero division abs_scaled_data = np.abs(single_data) / maxval diff --git a/src/graph_transpiler/webdnn/encoder/constant_encoder_raw.py b/src/graph_transpiler/webdnn/encoder/constant_encoder_raw.py index 428a0fcbd..67b11f2dd 100644 --- a/src/graph_transpiler/webdnn/encoder/constant_encoder_raw.py +++ b/src/graph_transpiler/webdnn/encoder/constant_encoder_raw.py @@ -1,6 +1,4 @@ -import numpy as np - -from webdnn.backend.interface.memory_layout import IMemoryLayout +from webdnn.backend.code_generator.allocator import MemoryLayout from webdnn.encoder.constant_encoder import ConstantEncoder @@ -8,5 +6,5 @@ class ConstantEncoderRaw(ConstantEncoder): def __init__(self): self.name = "raw" - def encode(self, constant_layout: IMemoryLayout, data: np.ndarray) -> bytes: - return data.tobytes("C") + def encode(self, memory_layout: MemoryLayout) -> bytes: + return memory_layout.data.tobytes("C") diff --git a/src/graph_transpiler/webdnn/frontend/chainer.py b/src/graph_transpiler/webdnn/frontend/chainer.py new file mode 100644 index 000000000..de245adbd --- /dev/null +++ b/src/graph_transpiler/webdnn/frontend/chainer.py @@ -0,0 +1,516 @@ +# -*- coding:utf-8 -*- + +""" +Chainer Link -> Graph object converters +Assuming Chainer 1.23 or 2.0 +""" + +from typing import List, Union, Type + +import chainer +import chainer.computational_graph +import numpy as np + +from webdnn.frontend.converter import Converter +from webdnn.graph.axis import Axis +from webdnn.graph.graph import Graph +from webdnn.graph.operator import Operator +from webdnn.graph.operators.average_pooling_2d import AveragePooling2D +from webdnn.graph.operators.axiswise_bias import AxiswiseBias +from webdnn.graph.operators.axiswise_scale import AxiswiseScale +from webdnn.graph.operators.clipped_relu import ClippedRelu +from webdnn.graph.operators.convolution2d import Convolution2D +from webdnn.graph.operators.deconvolution2d import Deconvolution2D +from webdnn.graph.operators.elementwise_sum import ElementwiseSum +from webdnn.graph.operators.elu import Elu +from webdnn.graph.operators.hard_sigmoid import HardSigmoid +from webdnn.graph.operators.leaky_relu import LeakyRelu +from webdnn.graph.operators.linear import Linear +from webdnn.graph.operators.local_response_normalization import LocalResponseNormalization +from webdnn.graph.operators.max_pooling_2d import MaxPooling2D +from webdnn.graph.operators.relu import Relu +from webdnn.graph.operators.reshape import Reshape +from webdnn.graph.operators.scalar_affine import ScalarAffine +from webdnn.graph.operators.sigmoid import Sigmoid +from webdnn.graph.operators.softmax import Softmax +from webdnn.graph.operators.softplus import Softplus +from webdnn.graph.operators.tanh import Tanh +from webdnn.graph.order import OrderNC, OrderNCHW, OrderC, OrderNHWC, OrderCNHW, Order, OrderCN, OrderHWNC, OrderHWCN +from webdnn.graph.variable import Variable +from webdnn.graph.variables.attributes.input import Input +from webdnn.graph.variables.attributes.output import Output +from webdnn.graph.variables.constant_variable import ConstantVariable +from webdnn.util import console +from webdnn.util.misc import mul + +if chainer.__version__ >= "2.": + chainer_v2 = True + # noinspection PyUnresolvedReferences + VariableNode = chainer.variable.VariableNode +else: + chainer_v2 = False + VariableNode = chainer.variable.Variable + + +def _to_variable_node(chainer_variable: Union[chainer.Variable, VariableNode]) -> VariableNode: + if chainer_v2 and not isinstance(chainer_variable, VariableNode): + # noinspection PyUnresolvedReferences + return chainer_variable.node + else: + return chainer_variable + + +class ChainerConverter(Converter[chainer.Function]): + """ + + c_var, c_opr: chainer variable and chainer operator(=chainer.Function) node object + n_var, n_opr: WebDNN IR variable and operator object + """ + + def convert_from_inout_vars(self, inputs: List[chainer.Variable], outputs: List[chainer.Variable]): + chainer_graph = chainer.computational_graph.build_computational_graph(outputs) + return self.convert(chainer_graph, inputs, outputs) + + def convert_core(self, chainer_computational_graph: chainer.computational_graph.ComputationalGraph, + input_c_vars: List[chainer.Variable], output_c_vars: List[chainer.Variable]) -> Graph: + # In chainer v2, variables are represented as Variable and VariableNode object, and + # graph information such as edge connection is contained in variable node. + # Therefore all chainer variable must be normalized into variable node. + input_c_vars = [_to_variable_node(v) for v in input_c_vars] + output_c_vars = [_to_variable_node(v) for v in output_c_vars] + + # Append InputVariable attribute to input variables + input_n_vars = [] + for c_var in input_c_vars: + n_var = self._convert_var(c_var) + n_var.attributes.add(Input(n_var)) + input_n_vars.append(n_var) + + self._convert_weight_vars(chainer_computational_graph) + + pending_c_oprs = [c_opr for c_opr in chainer_computational_graph.nodes if + isinstance(c_opr, chainer.Function)] + + while len(pending_c_oprs) > 0: + for c_opr in pending_c_oprs: + if all(((self.has_variable(_to_variable_node(c_var))) for c_var in c_opr.inputs)): + # All input variables of the `cfunc` are converted, so this `c_opr` can be converted. + self.convert_operator(c_opr) + pending_c_oprs.remove(c_opr) + break # for c_opr in pending_functions + else: + console.debug(pending_c_oprs) + raise ValueError("Inputs to functions cannot be resolved.") + + # Append OutputVariable attribute to output variables + output_n_vars = [] + for c_var in output_c_vars: + if not self.has_variable(c_var): + raise ValueError("Output variable is not generated by graph.") + n_var = self.get_variable(c_var) + n_var.attributes.add(Output) + output_n_vars.append(n_var) + + # Convert variable order into typical one in Chainer + self._transpose_vars() + + return Graph(input_n_vars, output_n_vars) + + def _convert_weight_vars(self, chainer_computational_graph: chainer.computational_graph.ComputationalGraph): + # Convert chainer variable which has name (= which is trained parameter) into WebDNN Variable object + + # special case + for c_var in chainer_computational_graph.nodes: + + # noinspection PyUnresolvedReferences + if isinstance(c_var, chainer.functions.normalization.batch_normalization.BatchNormalizationFunction): + # In chainer's BatchNormalization, mean and va (c_var.inputs[3] and c_var.inputs[4]) have no name, but + # they are trainable parameters. + if len(c_var.inputs) == 5: # data, gamma, bias, mean, var + self._convert_var(c_var.inputs[3], force_constant=True) + self._convert_var(c_var.inputs[4], force_constant=True) + + elif isinstance(c_var, chainer.functions.connection.deconvolution_2d.Deconvolution2DFunction): + # The order of weight of deconvolution2D in chainer is not Order NCHW, but OrderCNHW. + self._convert_var(c_var.inputs[1], force_constant=True, force_order=OrderCNHW) + + # general case + for c_var in chainer_computational_graph.nodes: + if isinstance(c_var, VariableNode): + if (not self.has_variable(c_var)) and c_var.name is not None: + self._convert_var(c_var) + + def _convert_var(self, c_var: VariableNode, force_constant=False, force_order: Order = None): + assert not self.has_variable(c_var), f"{c_var} is already converted" + ndim = len(c_var.shape) + if force_order: + order = force_order + + else: + if ndim == 4: + # both weight and variable + order = OrderNCHW + elif ndim == 2: + # both weight and variable + order = OrderNC + elif ndim == 1: + # both weight and variable + order = OrderC + else: + raise NotImplementedError(f"Unknown data format: {c_var}, ndim={c_var.ndim}, shape={c_var.shape}") + + assert order.ndim == ndim, f"Number of dimension is mismatched: order.ndim={order.ndim}, len(shape)={ndim}" + + if c_var.name is not None or force_constant: + n_var = ConstantVariable(chainer.cuda.to_cpu(c_var.data), order) # force on CPU + else: + n_var = Variable(c_var.shape, order) + + self.set_variable(c_var, n_var) + return n_var + + # noinspection PyMethodMayBeStatic + def _transpose_vars(self): + """ + Transpose variable order into typical chainer order. + """ + for n_var in self._variable_table[self.__class__.__name__].values(): + if isinstance(n_var, ConstantVariable): + if n_var.ndim == 1: + n_var.change_order(OrderC) + + elif n_var.ndim == 2: + n_var.change_order(OrderCN) + + elif n_var.ndim == 4: + assert len(n_var.input_to) == 1 + first_input_to = list(n_var.input_to)[0] + + if isinstance(first_input_to, Convolution2D): + n_var.change_order(OrderHWNC) + + elif isinstance(first_input_to, Deconvolution2D): + n_var.change_order(OrderHWNC) + + elif isinstance(first_input_to, Linear): + n_var.change_order(OrderHWCN) + + else: + raise NotImplementedError(f"Unknown data format: {n_var}") + + else: + if n_var.ndim == 1: + n_var.change_order(OrderC) + + elif n_var.ndim == 2: + n_var.change_order(OrderNC) + + elif n_var.ndim == 4: + n_var.change_order(OrderNHWC) + + +def register_activation(key: str, operator: Type[Operator]): + def _convert_activation(converter: ChainerConverter, c_opr: chainer.function.Function): + n_opr = operator(None) + assert len(c_opr.inputs) == 1, f"Number of input of {key} is invalid: Expected=1, Actual={len(c_opr.inputs)}" + y, = n_opr(converter.get_variable(c_opr.inputs[0])) + converter.set_variable(c_opr.outputs[0](), y) # c_opr.outputs: Iterable[Weakref[VariableNode]] + + ChainerConverter.register_handler(key)(_convert_activation) + + +register_activation("ELU", Elu) +register_activation("HardSigmoid", HardSigmoid) +register_activation("Sigmoid", Sigmoid) +register_activation("ReLU", Relu) +register_activation("Tanh", Tanh) + + +@ChainerConverter.register_handler("Softplus") +def _convert_softplus(converter: ChainerConverter, c_opr: chainer.functions.Softplus): + n_opr = Softplus(None, beta=c_opr.beta) + assert len(c_opr.inputs) == 1, f"Number of input of Softplus is invalid: Expected=1, Actual={len(c_opr.inputs)}" + y, = n_opr(converter.get_variable(c_opr.inputs[0])) + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("LeakyReLU") +def _convert_leaky_relu(converter: ChainerConverter, c_opr: chainer.functions.LeakyReLU): + n_opr = LeakyRelu(None, slope=c_opr.slope) + assert len(c_opr.inputs) == 1, f"Number of input of LeakyReLU is invalid: Expected=1, Actual={len(c_opr.inputs)}" + y, = n_opr(converter.get_variable(c_opr.inputs[0])) + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("ClippedReLU") +def _convert_clipped_relu(converter: ChainerConverter, c_opr: chainer.functions.ClippedReLU): + n_opr = ClippedRelu(None, cap=c_opr.cap) + assert len(c_opr.inputs) == 1, f"Number of input of ClippedReLU is invalid: Expected=1, Actual={len(c_opr.inputs)}" + y, = n_opr(converter.get_variable(c_opr.inputs[0])) + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("Softmax") +def _convert_softmax(converter: ChainerConverter, c_opr: chainer.functions.Softmax): + x = converter.get_variable(c_opr.inputs[0]) + n_opr = Softmax(None, axis=x.order.axes[c_opr.axis]) + assert len(c_opr.inputs) == 1, f"Number of input of Softmax is invalid: Expected=1, Actual={len(c_opr.inputs)}" + y, = n_opr(x) + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("LinearFunction") +def _convert_linear_function(converter: ChainerConverter, c_opr: chainer.functions.connection.linear.LinearFunction): + linear_opr = Linear(None) + + x = converter.get_variable(c_opr.inputs[0]) + w = converter.get_variable(c_opr.inputs[1]) + if x.ndim == 4 and w.ndim == 2: + # wを4次元に拡張 (NC -> NCHW) + x_shape_dict = x.shape_dict + w_shape_dict = w.shape_dict + assert x_shape_dict[Axis.C] * x_shape_dict[Axis.H] * x_shape_dict[Axis.W] == w_shape_dict[Axis.C] + assert w.order is OrderNC + w.order = OrderNCHW + w_new_shape = [w_shape_dict[Axis.N], x_shape_dict[Axis.C], x_shape_dict[Axis.H], + x_shape_dict[Axis.W]] + w.shape = w_new_shape + w.data = w.data.reshape(w_new_shape) + + y, = linear_opr(x, w) + if len(c_opr.inputs) == 3: + # with bias + bias_opr = AxiswiseBias(None, axis=Axis.C) + bias = converter.get_variable(c_opr.inputs[2]) + y, = bias_opr(y, bias) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("Convolution2DFunction") +def _convert_convolution2d_function(converter: ChainerConverter, + c_opr: chainer.functions.connection.convolution_2d.Convolution2DFunction): + x = converter.get_variable(c_opr.inputs[0]) + w = converter.get_variable(c_opr.inputs[1]) + + conv_opr = Convolution2D(None, + ksize=(w.shape_dict[Axis.H], w.shape_dict[Axis.W]), + stride=(c_opr.sy, c_opr.sx), + padding=(c_opr.ph, c_opr.pw)) + + y, = conv_opr(x, w) + + if len(c_opr.inputs) == 3: + # with bias + bias_opr = AxiswiseBias(None, axis=Axis.C) + bias = converter.get_variable(c_opr.inputs[2]) + y, = bias_opr(y, bias) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("DilatedConvolution2DFunction") +def _convert_dilated_convolution2d_function(converter: ChainerConverter, + c_opr: chainer.functions.connection.dilated_convolution_2d.DilatedConvolution2DFunction): + x = converter.get_variable(c_opr.inputs[0]) + w = converter.get_variable(c_opr.inputs[1]) + + # when dx == 1, it means ordinary convolution. + conv_opr = Convolution2D(None, + ksize=(w.shape_dict[Axis.H], w.shape_dict[Axis.W]), + stride=(c_opr.sy, c_opr.sx), + padding=(c_opr.ph, c_opr.pw), + dilation_rate=(c_opr.dx, c_opr.dy)) + + y, = conv_opr(x, w) + + if len(c_opr.inputs) == 3: + # with bias + bias_opr = AxiswiseBias(None, axis=Axis.C) + bias = converter.get_variable(c_opr.inputs[2]) + y, = bias_opr(y, bias) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("Deconvolution2DFunction") +def _convert_deconvolution2d_function(converter: ChainerConverter, + c_opr: chainer.functions.connection.deconvolution_2d.Deconvolution2DFunction): + x = converter.get_variable(c_opr.inputs[0]) + w = converter.get_variable(c_opr.inputs[1]) + + deconv_opr = Deconvolution2D(None, + ksize=(w.shape_dict[Axis.H], w.shape_dict[Axis.W]), + stride=(c_opr.sy, c_opr.sx), + padding=(c_opr.ph, c_opr.pw)) + + y, = deconv_opr(x, w) + + if len(c_opr.inputs) == 3: + # with bias + bias_opr = AxiswiseBias(None, axis=Axis.C) + bias = converter.get_variable(c_opr.inputs[2]) + y, = bias_opr(y, bias) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("MaxPooling2D") +def _convert_max_pooling2d(converter: ChainerConverter, + c_opr: chainer.functions.pooling.max_pooling_2d.MaxPooling2D): + if not c_opr.cover_all: + raise NotImplementedError("'cover_all=False' property in 'MaxPooling2D' is not supported.") + + x = converter.get_variable(c_opr.inputs[0]) + + pool_opr = MaxPooling2D(None, + ksize=(c_opr.kh, c_opr.kw), + stride=(c_opr.sy, c_opr.sx), + padding=(c_opr.ph, c_opr.pw)) + + y, = pool_opr(x) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("AveragePooling2D") +def _convert_average_pooling2d(converter: ChainerConverter, + c_opr: chainer.functions.pooling.average_pooling_2d.AveragePooling2D): + x = converter.get_variable(c_opr.inputs[0]) + + pool_opr = AveragePooling2D(None, + ksize=(c_opr.kh, c_opr.kw), + stride=(c_opr.sy, c_opr.sx), + padding=(c_opr.ph, c_opr.pw)) + + y, = pool_opr(x) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("LocalResponseNormalization") +def _convert_local_response_normalization(converter: ChainerConverter, + c_opr: chainer.functions.normalization.local_response_normalization.LocalResponseNormalization): + x = converter.get_variable(c_opr.inputs[0]) + + n_opr = LocalResponseNormalization(None, n=c_opr.n, k=c_opr.k, alpha=c_opr.alpha, beta=c_opr.beta) + + y, = n_opr(x) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("BatchNormalizationFunction") +def _convert_batch_normalization_function(converter: ChainerConverter, + c_opr: chainer.functions.normalization.batch_normalization.BatchNormalizationFunction): + x = converter.get_variable(c_opr.inputs[0]) + gamma = converter.get_variable(c_opr.inputs[1]) + beta = converter.get_variable(c_opr.inputs[2]) + + if len(c_opr.inputs) == 5: + # noinspection PyUnresolvedReferences + mean_data = converter.get_variable(c_opr.inputs[3]).data + # noinspection PyUnresolvedReferences + variance_data = converter.get_variable(c_opr.inputs[4]).data + + elif len(c_opr.inputs) == 3: + variance_data = c_opr.running_var + mean_data = c_opr.running_mean + + else: + raise ValueError("inputs to BatchNormalizationFunction have to be 5 or 3.") + console.debug(variance_data) + + # Simplify scale and bias + # + # from: + # y = (x - mean) / sqrt(var + eps) * gamma + beta + # + # to: + # y = x * gamma_div_std + beta_scaled + # + # gamma_div_std = gamma / sqrt(var + eps) + # beta_scaled = beta - mean * gamma_div_std + + # noinspection PyUnresolvedReferences + gamma_div_std = gamma.data / np.sqrt(variance_data + c_opr.eps) + # noinspection PyUnresolvedReferences + beta_scaled = beta.data - mean_data * gamma_div_std + + scale_opr = AxiswiseScale(None, axis=Axis.C) + gamma_div_std_const = ConstantVariable(gamma_div_std, OrderC) + scale_out, = scale_opr(x, gamma_div_std_const) + + offset_opr = AxiswiseBias(None, axis=Axis.C) + beta_scaled_const = ConstantVariable(beta_scaled, OrderC) + offset_out, = offset_opr(scale_out, beta_scaled_const) + + converter.set_variable(c_opr.outputs[0](), offset_out) + + +@ChainerConverter.register_handler("Add") +def _convert_add(converter: ChainerConverter, c_opr: chainer.functions.math.basic_math.Add): + xs = [converter.get_variable(x) for x in c_opr.inputs] + + n_opr = ElementwiseSum(None) + + y, = n_opr(*xs) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("AddConstant") +def _convert_add_constant(converter: ChainerConverter, c_opr: chainer.functions.math.basic_math.AddConstant): + x = converter.get_variable(c_opr.inputs[0]) + + n_opr = ScalarAffine(None, scale=1, bias=float(c_opr.value)) + + y, = n_opr(x) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("MulConstant") +def _convert_mul_constant(converter: ChainerConverter, c_opr: chainer.functions.math.basic_math.MulConstant): + x = converter.get_variable(c_opr.inputs[0]) + + n_opr = ScalarAffine(None, scale=float(c_opr.value), bias=0.0) + + y, = n_opr(x) + + converter.set_variable(c_opr.outputs[0](), y) + + +@ChainerConverter.register_handler("Reshape") +def _convert_reshape(converter: ChainerConverter, c_opr: chainer.functions.array.reshape.Reshape): + assert len(c_opr.inputs) == 1, \ + f"For 'Reshape' operator in chainer, expected number of inputs is 1, but actual is {len(c_opr.inputs)}" + + x = converter.get_variable(c_opr.inputs[0]) + + out_shape = c_opr.shape + if len(out_shape) == 1: + out_order = OrderC + elif len(out_shape) == 2: + out_order = OrderNC + elif len(out_shape) == 4: + out_order = OrderNCHW + else: + raise NotImplementedError("Reshaping into dimensions none of 1, 2, 4 is not supported.") + assert mul(out_shape) == x.size + + n_opr = Reshape(None, in_order=x.order, out_order=out_order, out_shape=out_shape) + + y, = n_opr(x) + + converter.set_variable(c_opr.outputs[0](), y) + + +# noinspection PyUnresolvedReferences +@ChainerConverter.register_handler("Dropout") +def _convert_dropout(converter: ChainerConverter, c_opr: chainer.functions.noise.dropout.Dropout): + console.warning("Dropout is omitted") + + x = converter.get_variable(c_opr.inputs[0]) + + converter.set_variable(c_opr.outputs[0](), x) diff --git a/src/graph_transpiler/webdnn/frontend/converter.py b/src/graph_transpiler/webdnn/frontend/converter.py new file mode 100644 index 000000000..51bd3120a --- /dev/null +++ b/src/graph_transpiler/webdnn/frontend/converter.py @@ -0,0 +1,136 @@ +from abc import abstractmethod +from collections import defaultdict +from typing import Callable, Dict, TypeVar, Generic + +from webdnn.graph.graph import Graph +from webdnn.graph.operator import Operator +from webdnn.graph.variable import Variable +from webdnn.util import console + +T_OP = TypeVar('T_OP') + + +class Converter(Generic[T_OP]): + """Converter base class + + This class converts computation graph in some DNN library into WebDNN IR format. + + To use this class, you should implement :func:`~webdnn.converter.Converter.convert_core` and + convert handler for each operator (Please see :func:`~webdnn.converter.Converter.register_handler`). + """ + + """ + For each concrete Converter + """ + _handler_map = defaultdict(dict) # type: Dict[str, Dict[str, Callable[["Converter", T_OP], Operator]]] + _variable_table = defaultdict(dict) # type: Dict[str, Dict[object, Variable]] + + @abstractmethod + def convert_core(self, *args, **kwargs) -> Graph: + """Convert computation graph into WebDNN IR format. + + This is main routine of converter. You need to implement follow operations. + + 1. Traverse given computation graph and for each operator, call + :func:`~webdnn.converter.Converter.convert_operator` with the operator and variables. + This method converts the operator in IR format and build computation graph around this operator. + + 2. Build and return computation graph. This procedure can be implemented simply like follows:: + + Returns: + Graph in WebDNN IR format + """ + raise NotImplementedError + + @classmethod + def register_handler(cls, key: str): + """Decorator to register operator converter handler + + You need to implement handlers for all operators you want to supports. Each handler is consisted as follows: + + 1. Call :func:`~webdnn.converter.Converter.convert_variable` for each input and output variables. + This method converts the variable in IR format. + + 2. Connect computation graph. + + For example, considering follows sequential graph in framework A. let op1 and op2 be instances of + `A.SomeOperator` + + .. code-block:: text + + v1 -[op1]-> v2 -[op2]-> v3 + + Converter handler can be implemented as follows:: + + @A_Converter.register_handler("SomeOperator") + def _handler(converter: A_Converter, op_A: A.SomeOperator): + # convert operator into WebDNN IR + op_webdnn = webdnn.graph.operators.SomeOperator(op_A.some_hyper_parameter) + + # connect input variable + if converter.has_variable(op_A.inputs[0]): + x = self.get_variable(op_A.inputs[0]) + + else: + x = webdnn.graph.variable.Variable(op_A.inputs[0].shape, OrderNHWC) + self.set_variable(op_A.inputs[0], x) + + # connect output variable + if converter.has_variable(op_A.outputs[0]): + y = self.get_variable(op_A.outputs[0]) + y_dummy = op_WebDNN(x) + op.replace_output(y_dummy, y) + + else: + y = op_WebDNN(x) + self.set_variable(op.outputs[0], y) + + Args: + key: operator type name. As default, it's the class name for each operator instance. you can change this + behavior by overriding :func:`~webdnn.converter.Converter.serialize_operator_type`. + """ + + def decorator(handler: Callable[["Converter", T_OP], Operator]): + if key in cls._handler_map[cls.__name__]: + console.warning(f"[f{cls.__name__}] Converter Handler for '{key}' is already registered in {cls.__name__} and overwritten.") + + cls._handler_map[cls.__name__][key] = handler + + return decorator + + def serialize_operator_type(self, operator: T_OP) -> str: + return operator.__class__.__name__ + + def get_variable(self, key: object) -> Variable: + """Gets variable object corresponding to the key. + + Returns: + variable object in WebDNN IR Format. + """ + return self._variable_table[self.__class__.__name__][key] + + def set_variable(self, key: object, variable: Variable): + """Stores variable object corresponding to the key. + + """ + if key in self._variable_table[self.__class__.__name__]: + raise ValueError(f"Variable {key} already exists") + self._variable_table[self.__class__.__name__][key] = variable + + def has_variable(self, key: object) -> bool: + """Check whether variable object corresponding to the key is generated or not. + + """ + return key in self._variable_table[self.__class__.__name__] + + def convert(self, *args, **kwargs) -> Graph: + return self.convert_core(*args, **kwargs) + + def convert_operator(self, operator: T_OP): + operator_key = self.serialize_operator_type(operator) + if operator_key not in self._handler_map[self.__class__.__name__].keys(): + raise NotImplementedError(f"Operator '{operator_key}' is not handled any converter handlers.") + + self._handler_map[self.__class__.__name__][operator_key](self, operator) + + return None diff --git a/src/graph_transpiler/webdnn/frontend/general_optimize_rule.py b/src/graph_transpiler/webdnn/frontend/general_optimize_rule.py deleted file mode 100644 index 6f46d7980..000000000 --- a/src/graph_transpiler/webdnn/frontend/general_optimize_rule.py +++ /dev/null @@ -1,13 +0,0 @@ -from webdnn.frontend.sub_rules.concat_affine import ConcatAffine -from webdnn.frontend.sub_rules.concat_scalar_affine import ConcatScalarAffine -from webdnn.frontend.sub_rules.remove_last_softmax import RemoveLastSoftmax -from webdnn.graph.optimize_rule import OptimizeRule - - -class GeneralOptimizeRule(OptimizeRule): - def __init__(self): - super(GeneralOptimizeRule, self).__init__() - - self.register(RemoveLastSoftmax()) - self.register(ConcatAffine()) - self.register(ConcatScalarAffine()) diff --git a/src/graph_transpiler/webdnn/frontend/keras.py b/src/graph_transpiler/webdnn/frontend/keras.py new file mode 100644 index 000000000..a0ea76444 --- /dev/null +++ b/src/graph_transpiler/webdnn/frontend/keras.py @@ -0,0 +1,775 @@ +# -*- coding:utf-8 -*- + +""" +Keras model -> Graph object converters +Assuming Keras 2.0.4 + +Currently, the system assumes the model is trained with "data_format" == "channels_last". +If this is not the case, Flatten layer which follows Convolution have to change the order of variable. +Convolution implementation is currently assuming variable is NHWC. +""" + +import json +from typing import List, Tuple, Dict +from typing import Optional + +import h5py +import numpy as np + +from webdnn.frontend.converter import Converter +from webdnn.graph.axis import Axis +from webdnn.graph.graph import Graph +from webdnn.graph.operator import Operator +from webdnn.graph.operators.average_pooling_2d import AveragePooling2D +from webdnn.graph.operators.axiswise_bias import AxiswiseBias +from webdnn.graph.operators.axiswise_scale import AxiswiseScale +from webdnn.graph.operators.concat import Concat +from webdnn.graph.operators.convolution2d import Convolution2D +from webdnn.graph.operators.elementwise_sum import ElementwiseSum +from webdnn.graph.operators.embedding import Embedding +from webdnn.graph.operators.hard_sigmoid import HardSigmoid +from webdnn.graph.operators.linear import Linear +from webdnn.graph.operators.lstm import LSTM +from webdnn.graph.operators.max_pooling_2d import MaxPooling2D +from webdnn.graph.operators.reinterpret_axis import ReinterpretAxis +from webdnn.graph.operators.relu import Relu +from webdnn.graph.operators.reshape import Reshape +from webdnn.graph.operators.sigmoid import Sigmoid +from webdnn.graph.operators.softmax import Softmax +from webdnn.graph.operators.softplus import Softplus +from webdnn.graph.operators.softsign import Softsign +from webdnn.graph.operators.zero_padding_1d import ZeroPadding1D +from webdnn.graph.operators.zero_padding_2d import ZeroPadding2D +from webdnn.graph.order import OrderNC, OrderC, OrderCN, OrderHWCN, \ + OrderNHWC, Order, OrderNT, OrderNTC +from webdnn.graph.variable import Variable +from webdnn.graph.variables.constant_variable import ConstantVariable +from webdnn.util import console +from webdnn.util.misc import mul + + +class KerasInput: + inbound_layer_name: str + node_index: int + tensor_index: int + kwargs: Dict + key: Tuple[str, int, int] + + def __init__(self, inbound_layer_name: str, node_index: int, tensor_index: int, kwargs: Optional[Dict] = None): + self.inbound_layer_name = inbound_layer_name + self.node_index = node_index + self.tensor_index = tensor_index + self.kwargs = kwargs or {} + self.key = (inbound_layer_name, node_index, tensor_index) + + +class KerasOperator: + class_name: str + name: str + inputs: List[Variable] + inputs_kwargs: List[Dict[str, object]] + outputs: List[Variable] + specific_config: Dict[str, object] + + def __init__(self, layer_config, inputs: List[Variable], inputs_kwargs: List[Dict[str, object]]): + self.class_name = layer_config["class_name"] + self.name = layer_config["config"]["name"] + self.specific_config = layer_config["config"] + self.inputs = inputs + self.inputs_kwargs = inputs_kwargs + self.outputs = None + + +class KerasConverter(Converter[KerasOperator]): + _weight_dataset: h5py.Group + _global_input_variables: List[Variable] + _global_output_variables: List[Variable] + _variable_table_stack = List[Dict[object, Variable]] + _container_name_stack = List[str] + _constant_variables: Dict[str, ConstantVariable] + + def convert_core(self, model: h5py.File, input_shapes: List[List[int]]) -> Graph: + model_config = json.loads(model.attrs["model_config"]) + + self._variable_table_stack = [] + self._container_name_stack = [] + self._constant_variables = {} + self._global_input_variables = [] + for input_shape in input_shapes: + order = None + if len(input_shape) == 1: + order = OrderC + elif len(input_shape) == 2: + order = OrderNC + elif len(input_shape) == 3: + order = OrderNTC + elif len(input_shape) == 4: + # Assuming data_format == "channels_last": + order = OrderNHWC + else: + raise NotImplementedError("Input shape must be 1,2,3,4 dimensions") + v = Variable(input_shape, order) + self._global_input_variables.append(v) + self._weight_dataset = model["model_weights"] + if model_config["class_name"] == "Sequential": + self.convert_core_sequential(model_config) + elif model_config["class_name"] == "Model": + self.convert_core_model(model_config) + else: + raise NotImplementedError("Unknown model type") + return Graph(self._global_input_variables, self._global_output_variables) + + def serialize_operator_type(self, operator: KerasOperator) -> str: + return operator.class_name + + def convert_core_sequential(self, layer_config: Dict[str, object]): + top_level = "inbound_nodes" not in layer_config + if top_level: + # top-level container + assert len(self._global_input_variables) == 1, "sequential model can take only one input variable" + input_nodes = [[self._global_input_variables[0]]] + else: + self._container_name_stack.append(layer_config["name"]) + input_nodes = [] + for inbound_node in layer_config["inbound_nodes"]: + assert len(inbound_node) == 1, "sequential model can take only one input variable" + input_var_info = inbound_node[0] # ['conv2d_2', 0, 0, {}] + assert input_var_info[3] == {}, "input node to sequential model cannot have arguments" + input_nodes.append([self.get_variable(tuple(input_var_info[:3]))]) + + for i, input_node in enumerate(input_nodes): + node_index = i + 1 # Container node_index origin from 1 + # generate operator objects + current_var = input_node[0] + self._variable_table_stack.append(self._variable_table[self.__class__.__name__]) + self._variable_table[self.__class__.__name__] = {} + for serial_index, sub_layer_config in enumerate(layer_config["config"]): + if sub_layer_config["class_name"] == "Sequential": + self.convert_core_sequential(sub_layer_config) + elif sub_layer_config["class_name"] == "Model": + self.convert_core_model(sub_layer_config) + else: + operator = KerasOperator(sub_layer_config, [current_var], [{}]) + self.convert_operator(operator) + current_var = operator.outputs[0] + + self._variable_table[self.__class__.__name__] = self._variable_table_stack.pop() + # output of whole graph = output of final layer + if top_level: + self._global_output_variables = [current_var] + else: + self.set_variable((layer_config["name"], node_index, 0), current_var) + + if not top_level: + self._container_name_stack.pop() + + def convert_core_model(self, layer_config: Dict[str, object]): + top_level = "inbound_nodes" not in layer_config + if top_level: + # top-level container + input_nodes = [self._global_input_variables] + self._global_output_variables = [] + else: + self._container_name_stack.append(layer_config["name"]) + input_nodes = [] + for inbound_node in layer_config["inbound_nodes"]: + input_node = [] + for input_var_info in inbound_node: # ['conv2d_2', 0, 0, {}] + assert input_var_info[3] == {}, "input node to graph model cannot have arguments" + input_node.append(self.get_variable(tuple(input_var_info[:3]))) + input_nodes.append(input_node) + + for i, input_node in enumerate(input_nodes): + node_index = i + 1 # Container node_index origin from 1 + # generate operator objects + self._variable_table_stack.append(self._variable_table[self.__class__.__name__]) + local_variable_table = {} + # assign inbound_node to local variable table + for j, input_key in enumerate(layer_config["config"]["input_layers"]): # input_key: ['input_1', 0, 0] + # variable to this container is referred with input_key + local_variable_table[tuple(input_key)] = input_node[j] + self._variable_table[self.__class__.__name__] = local_variable_table + for serial_index, sub_layer_config in enumerate(layer_config["config"]["layers"]): + if sub_layer_config["class_name"] == "Sequential": + self.convert_core_sequential(sub_layer_config) + elif sub_layer_config["class_name"] == "Model": + self.convert_core_model(sub_layer_config) + else: + for sub_layer_node_index, sub_layer_inbound_node in enumerate(sub_layer_config["inbound_nodes"]): + # 'inbound_nodes': [[['input_2', 0, 0, {}]], [['conv2d_2', 0, 0, {}]]] + # gather input variables + sub_layer_inputs = [] + sub_layer_input_kwargs = [] + for sub_layer_input_var_info in sub_layer_inbound_node: + sub_layer_inputs.append(self.get_variable(tuple(sub_layer_input_var_info[:3]))) + sub_layer_input_kwargs.append(sub_layer_input_var_info[3]) + + operator = KerasOperator(sub_layer_config, sub_layer_inputs, sub_layer_input_kwargs) + self.convert_operator(operator) + + # assign outputs + for sub_layer_output_tensor_index, sub_layer_output_var in enumerate(operator.outputs): + local_variable_table[(sub_layer_config["name"], sub_layer_node_index, + sub_layer_output_tensor_index)] = sub_layer_output_var + + self._variable_table[self.__class__.__name__] = self._variable_table_stack.pop() + # output of whole graph is desginated in layer_config["output_layers"] + for output_tensor_index, output_layer in enumerate(layer_config["config"]["output_layers"]): + # local ['conv2d_3', 0, 0] -> global ['model_1', 1, 0] + if top_level: + self._global_output_variables.append(local_variable_table[tuple(output_layer)]) + else: + self.set_variable((layer_config["name"], node_index, output_tensor_index), + local_variable_table[tuple(output_layer)]) + + if not top_level: + self._container_name_stack.pop() + + def _get_weight_value(self, weight_key: str) -> np.ndarray: + try: + weight_object = self._weight_dataset[weight_key] + except KeyError: + try: + weight_key_split = weight_key.split('/') + weight_alter_key = f"{weight_key_split[0]}/{weight_key_split[1]}_1/{weight_key_split[2]}" + weight_object = self._weight_dataset[weight_alter_key] + except KeyError: + console.error(f"Weight parameter {weight_key} or {weight_alter_key} does not exist in model file.") + raise + return weight_object.value + + def create_constant_array(self, operator: KerasOperator, key: str) -> np.ndarray: + # when the operator is top-level, key is "{operator.name}/{operator.name}/{key}" + # if contained in nested container, key is "{container.name}/{operator.name}/{key}" + weight_key = self._get_key_for_constant_variable(operator, key) + return self._get_weight_value(weight_key) + + def _get_key_for_constant_variable(self, operator: KerasOperator, key: str) -> str: + weight_key = f"{self._container_name_stack[-1] if len(self._container_name_stack) > 0 else operator.name}/{operator.name}/{key}" + return weight_key + + def create_constant_variable(self, operator: KerasOperator, key: str, order: Order, + force_new: bool = False) -> ConstantVariable: + weight_key = self._get_key_for_constant_variable(operator, key) + # force_new should be used when the contents of constant variable may be modified + if not force_new and weight_key in self._constant_variables: + cvar = self._constant_variables[weight_key] + assert cvar.order == order + return cvar + + cvar = ConstantVariable(self.create_constant_array(operator, key), order) + self._constant_variables[weight_key] = cvar + return cvar + + +def do_activation(activation_type: str, x: Variable) -> Variable: + act_opr = None + if activation_type == "relu": + act_opr = Relu(None) + elif activation_type == "sigmoid": + act_opr = Sigmoid(None) + elif activation_type == "hard_sigmoid": + act_opr = HardSigmoid(None) + elif activation_type == "softplus": + act_opr = Softplus(None, beta=1.0) + elif activation_type == "softsign": + act_opr = Softsign(None) + elif activation_type == "softmax": + act_opr = Softmax(None, axis=x.order.axes[-1]) + elif activation_type == "linear": + pass + else: + raise NotImplementedError(f"Unknown activation {activation_type}") + + if act_opr is not None: + x, = act_opr(x) + + return x + + +@KerasConverter.register_handler("InputLayer") +def _convert_input_layer(converter: KerasConverter, operator: KerasOperator): + """ + Dummy layer + Args: + converter: + operator: + + Returns: + + """ + pass + + +@KerasConverter.register_handler("Dense") +def _convert_dense(converter: KerasConverter, operator: KerasOperator): + assert len(operator.inputs) == 1 + x = operator.inputs[0] + w = converter.create_constant_variable(operator, "kernel:0", OrderCN) + linear_opr = Linear(None) + + y, = linear_opr(x, w) + + if operator.specific_config["use_bias"]: + bias = converter.create_constant_variable(operator, "bias:0", OrderC) + bias_opr = AxiswiseBias(None, Axis.C) + y, = bias_opr(y, bias) + + y = do_activation(operator.specific_config["activation"], y) + + operator.outputs = [y] + + +@KerasConverter.register_handler("Dropout") +def _convert_dropout(converter: KerasConverter, operator: KerasOperator): + x = operator.inputs[0] + console.warning("[KerasConverter] omitting dropout") + + operator.outputs = [x] + + +@KerasConverter.register_handler("Conv2D") +def _convert_conv2d(converter: KerasConverter, operator: KerasOperator): + """ + Example: + {'class_name': 'Conv2D', +'config': {'activation': 'relu', +'activity_regularizer': None, +'bias_constraint': None, +'bias_initializer': {'class_name': 'Zeros', 'config': {}}, +'bias_regularizer': None, +'data_format': 'channels_last', +'dilation_rate': [1, 1], +'filters': 64, +'kernel_constraint': None, +'kernel_initializer': {'class_name': 'VarianceScaling', + 'config': {'distribution': 'uniform', + 'mode': 'fan_avg', + 'scale': 1.0, + 'seed': None}}, +'kernel_regularizer': None, +'kernel_size': [3, 3], +'name': 'conv2d_2', +'padding': 'valid', +'strides': [1, 1], +'trainable': True, +'use_bias': True}}, + :param layer_config: + :param inputs: + :return: + """ + assert len(operator.inputs) == 1 + x = operator.inputs[0] + assert operator.specific_config["data_format"] == "channels_last" + w = converter.create_constant_variable(operator, "kernel:0", OrderHWCN) # order does not depend on data_format + ksize: Tuple[int, int] = tuple(operator.specific_config["kernel_size"]) + stride: Tuple[int, int] = tuple(operator.specific_config["strides"]) + dilation_rate: Tuple[int, int] = tuple(operator.specific_config.get("dilation_rate", 1)) + padding_keras: str = operator.specific_config["padding"] # valid or same + if padding_keras == "valid": + padding = (0, 0) + elif padding_keras == "same": + padding = (ksize[0] // 2, ksize[1] // 2) + else: + raise ValueError("Unknown padding") + + conv2d_opr = Convolution2D(None, + ksize=ksize, + stride=stride, + padding=padding, + dilation_rate=dilation_rate) + y, = conv2d_opr(x, w) + + if operator.specific_config["use_bias"]: + bias = converter.create_constant_variable(operator, "bias:0", OrderC) + bias_opr = AxiswiseBias(None, Axis.C) + y, = bias_opr(y, bias) + + y = do_activation(operator.specific_config["activation"], y) + + operator.outputs = [y] + + +@KerasConverter.register_handler("MaxPooling2D") +def _convert_max_pooling2d(converter: KerasConverter, operator: KerasOperator): + """ + Example: + {'class_name': 'MaxPooling2D', + 'config': {'data_format': 'channels_last', + 'name': 'max_pooling2d_1', + 'padding': 'valid', + 'pool_size': [2, 2], + 'strides': [2, 2], + 'trainable': True}}, + """ + assert len(operator.inputs) == 1 + x = operator.inputs[0] + ksize: Tuple[int, int] = tuple(operator.specific_config["pool_size"]) + stride: Tuple[int, int] = tuple(operator.specific_config["strides"]) + padding_keras: str = operator.specific_config["padding"] # valid or same + if padding_keras == "valid": + padding = (0, 0) + elif padding_keras == "same": + padding = (ksize[0] // 2, ksize[1] // 2) + else: + raise ValueError("Unknown padding") + + max_pooling_2d_opr = MaxPooling2D(None, + ksize=ksize, + stride=stride, + padding=padding) + y, = max_pooling_2d_opr(x) + + operator.outputs = [y] + + +@KerasConverter.register_handler("AveragePooling2D") +def convert_layer_average_pooling2d(converter: KerasConverter, operator: KerasOperator): + """ + Example: +{'class_name': 'AveragePooling2D', +'config': {'data_format': 'channels_last', +'name': 'avg_pool', +'padding': 'valid', +'pool_size': [7, 7], +'strides': [7, 7], +'trainable': True}, +'inbound_nodes': [[['activation_49', 0, 0, {}]]], +'name': 'avg_pool'}, + + :param layer_config: + :param inputs: + :return: + """ + + assert len(operator.inputs) == 1 + x = operator.inputs[0] + ksize: Tuple[int, int] = tuple(operator.specific_config["pool_size"]) + stride: Tuple[int, int] = tuple(operator.specific_config["strides"]) + padding_keras: str = operator.specific_config["padding"] # valid or same + if padding_keras == "valid": + padding = (0, 0) + elif padding_keras == "same": + padding = (ksize[0] // 2, ksize[1] // 2) + else: + raise ValueError("Unknown padding") + + average_pooling_2d_opr = AveragePooling2D(None, + ksize=ksize, + stride=stride, + padding=padding) + y, = average_pooling_2d_opr(x) + + operator.outputs = [y] + + +@KerasConverter.register_handler("GlobalAveragePooling2D") +def convert_layer_global_average_pooling2d(converter: KerasConverter, operator: KerasOperator): + """ + Example: + {'class_name': 'GlobalAveragePooling2D', +'config': {'data_format': 'channels_last', + 'name': 'global_average_pooling2d_1', + 'trainable': True}, +'inbound_nodes': [[['add_2', 0, 0, {}]]], +'name': 'global_average_pooling2d_1'}, + + :param layer_config: + :param inputs: + :return: + """ + + assert len(operator.inputs) == 1 + x = operator.inputs[0] + + ksize: Tuple[int, int] = (x.shape_dict[Axis.H], x.shape_dict[Axis.W]) + average_pooling_2d_opr = AveragePooling2D(None, + ksize=ksize, + stride=(1, 1), + padding=(0, 0)) + y, = average_pooling_2d_opr(x) + + # flatten without changing memory layout + reshape_opr = Reshape(None, in_order=y.order, out_order=OrderNC, out_shape=[y.shape[0], mul(y.shape[1:])]) + z, = reshape_opr(y) + + operator.outputs = [z] + + +@KerasConverter.register_handler("Flatten") +def _convert_flatten(converter: KerasConverter, operator: KerasOperator): + """ + Example: + {'class_name': 'Flatten', +'config': {'name': 'flatten_1', 'trainable': True}}, + + :param layer_config: + :param inputs: + :return: + """ + assert len(operator.inputs) == 1 + x = operator.inputs[0] + assert x.order.axes[0] == Axis.N + reshape_opr = Reshape(None, in_order=x.order, out_order=OrderNC, out_shape=[x.shape[0], mul(x.shape[1:])]) + y, = reshape_opr(x) + + operator.outputs = [y] + + +@KerasConverter.register_handler("Concatenate") +def _convert_concatenate(converter: KerasConverter, operator: KerasOperator): + """ + Example: + {'name': 'mixed0', 'trainable': True, 'axis': 3} + + :param layer_config: + :param inputs: + :return: + """ + xs = operator.inputs + axis = xs[0].order.axes[operator.specific_config["axis"]] + concat_opr = Concat(None, axis=axis) + y, = concat_opr(*xs) + + operator.outputs = [y] + + +@KerasConverter.register_handler("Add") +def _convert_add(converter: KerasConverter, operator: KerasOperator): + """ + Example: + {'class_name': 'Add', + 'config': {'name': 'add_1', 'trainable': True}, + 'inbound_nodes': [[['conv2d_2', 0, 0, {}], ['conv2d_3', 0, 0, {}]]], + 'name': 'add_1'}, + :param layer_config: + :param inputs: + :return: + """ + xs = operator.inputs + sum_opr = ElementwiseSum(None) + y, = sum_opr(*xs) + + operator.outputs = [y] + + +@KerasConverter.register_handler("Activation") +def _convert_activation(converter: KerasConverter, operator: KerasOperator): + assert len(operator.inputs) == 1 + y = operator.inputs[0] + + y = do_activation(operator.specific_config["activation"], y) + + operator.outputs = [y] + + +@KerasConverter.register_handler("BatchNormalization") +def _convert_batch_normalization(converter: KerasConverter, operator: KerasOperator): + """ + Example: +{'class_name': 'BatchNormalization', +'config': {'axis': 3, +'beta_constraint': None, +'beta_initializer': {'class_name': 'Zeros', 'config': {}}, +'beta_regularizer': None, +'center': True, +'epsilon': 0.001, +'gamma_constraint': None, +'gamma_initializer': {'class_name': 'Ones', 'config': {}}, +'gamma_regularizer': None, +'momentum': 0.99, +'moving_mean_initializer': {'class_name': 'Zeros', 'config': {}}, +'moving_variance_initializer': {'class_name': 'Ones', 'config': {}}, +'name': 'bn2a_branch2a', +'scale': True, +'trainable': True}, +'inbound_nodes': [[['res2a_branch2a', 0, 0, {}]]], +'name': 'bn2a_branch2a'}, + + :param layer_config: + :param inputs: + :return: + """ + assert len(operator.inputs) == 1 + x = operator.inputs[0] + + axis = x.order.axes[operator.specific_config["axis"]] + mean = converter.create_constant_array(operator, "moving_mean:0") + variance = converter.create_constant_array(operator, "moving_variance:0") + + if operator.specific_config["scale"]: + gamma = converter.create_constant_array(operator, "gamma:0") + else: + gamma = np.ones_like(variance) + + if operator.specific_config["center"]: + beta = converter.create_constant_array(operator, "beta:0") + else: + beta = np.ones_like(variance) + + gamma_div_std = gamma / np.sqrt(variance + operator.specific_config["epsilon"]) + beta_scaled = beta - mean * gamma_div_std + + scale_opr = AxiswiseScale(None, axis=axis) + bias_opr = AxiswiseBias(None, axis=axis) + scale_out, = scale_opr(x, ConstantVariable(gamma_div_std, OrderC)) + y, = bias_opr(scale_out, ConstantVariable(beta_scaled, OrderC)) + + operator.outputs = [y] + + +@KerasConverter.register_handler("ZeroPadding2D") +def _convert_zero_padding2d(converter: KerasConverter, operator: KerasOperator): + """ + Example: + {'class_name': 'ZeroPadding2D', + 'config': {'data_format': 'channels_last', + 'name': 'zero_padding2d_1', + 'padding': [[3, 3], [3, 3]], + 'trainable': True}, + 'inbound_nodes': [[['input_1', 0, 0, {}]]], + 'name': 'zero_padding2d_1'}, + :param layer_config: + :param inputs: + :return: + """ + assert len(operator.inputs) == 1 + x = operator.inputs[0] + + padding = operator.specific_config["padding"] + top = padding[0][0] + if top != padding[0][1]: + raise ValueError("Padding size of top and bottom must be same.") + left = padding[1][0] + if left != padding[1][1]: + raise ValueError("Padding size of left and right must be same.") + + pad_opr = ZeroPadding2D(None, (top, left)) + y, = pad_opr(x) + + operator.outputs = [y] + + +@KerasConverter.register_handler("ZeroPadding1D") +def _convert_zero_padding1d(converter: KerasConverter, operator: KerasOperator): + """ + {'class_name': 'ZeroPadding1D', + 'config': {'name': 'zero_padding1d_1', + 'padding': [0, 1], + 'trainable': True}}, + + Args: + converter: + operator: + + Returns: + + """ + assert len(operator.inputs) == 1 + x = operator.inputs[0] + assert x.order == OrderNTC + pad_opr = ZeroPadding1D(None, padding=tuple(operator.specific_config["padding"])) + + y, = pad_opr(x) + + operator.outputs = [y] + + +@KerasConverter.register_handler("Embedding") +def _convert_embedding(converter: KerasConverter, operator: KerasOperator): + """ + {'class_name': 'Embedding', + 'config': {'activity_regularizer': None, + 'batch_input_shape': [None, None], + 'dtype': 'int32', + 'embeddings_constraint': None, + 'embeddings_initializer': {'class_name': 'RandomUniform', + 'config': {'maxval': 0.05, 'minval': -0.05, 'seed': None}}, + 'embeddings_regularizer': None, + 'input_dim': 20000, + 'input_length': None, + 'mask_zero': False, + 'name': 'embedding_1', + 'output_dim': 128, + 'trainable': True}} + + Args: + converter: + operator: + + Returns: + + """ + assert len(operator.inputs) == 1 + x = operator.inputs[0] + if x.order == OrderNC: + # re-interpret as NT + reinterpret_opr = ReinterpretAxis(None, in_order=OrderNC, out_order=OrderNT) + x, = reinterpret_opr(x) + w = converter.create_constant_variable(operator, "embeddings:0", OrderCN) + embedding_opr = Embedding(None) + + y, = embedding_opr(x, w) + + operator.outputs = [y] + + +@KerasConverter.register_handler("LSTM") +def _convert_lstm(converter: KerasConverter, operator: KerasOperator): + """ + {'class_name': 'LSTM', + 'config': {'activation': 'tanh', + 'activity_regularizer': None, + 'bias_constraint': None, + 'bias_initializer': {'class_name': 'Zeros', 'config': {}}, + 'bias_regularizer': None, + 'dropout': 0.2, + 'go_backwards': False, + 'implementation': 0, + 'kernel_constraint': None, + 'kernel_initializer': {'class_name': 'VarianceScaling', + 'config': {'distribution': 'uniform', + 'mode': 'fan_avg', + 'scale': 1.0, + 'seed': None}}, + 'kernel_regularizer': None, + 'name': 'lstm_1', + 'recurrent_activation': 'hard_sigmoid', + 'recurrent_constraint': None, + 'recurrent_dropout': 0.2, + 'recurrent_initializer': {'class_name': 'Orthogonal', + 'config': {'gain': 1.0, 'seed': None}}, + 'recurrent_regularizer': None, + 'return_sequences': False, + 'stateful': False, + 'trainable': True, + 'unit_forget_bias': True, + 'units': 128, + 'unroll': False, + 'use_bias': True}} + + Args: + converter: + operator: + + Returns: + + """ + assert len(operator.inputs) == 1 + assert operator.specific_config["stateful"] is False + assert operator.specific_config["go_backwards"] is False + x = operator.inputs[0] + w_input = converter.create_constant_variable(operator, "kernel:0", OrderCN) + w_hidden = converter.create_constant_variable(operator, "recurrent_kernel:0", OrderCN) + if operator.specific_config["use_bias"]: + b = converter.create_constant_variable(operator, "bias:0", OrderC) + else: + b = None + lstm_opr = LSTM(None, operator.specific_config["use_bias"], operator.specific_config["return_sequences"], + use_initial_c=False, use_initial_h=False, + activation=operator.specific_config["activation"], + recurrent_activation=operator.specific_config["recurrent_activation"]) + + y, _ = lstm_opr(x, w_input, w_hidden, b) + + operator.outputs = [y] diff --git a/src/graph_transpiler/webdnn/graph/attribute.py b/src/graph_transpiler/webdnn/graph/attribute.py index 069ca4b57..f5ed613be 100644 --- a/src/graph_transpiler/webdnn/graph/attribute.py +++ b/src/graph_transpiler/webdnn/graph/attribute.py @@ -1,8 +1,8 @@ -from webdnn.graph.interface import INode, IAttribute +from webdnn.graph import node -class Attribute(IAttribute): - node: INode +class Attribute: + base: "node.Node" - def __init__(self, node: INode): - self.node = node + def __init__(self, base: "node.Node"): + self.base = base diff --git a/src/graph_transpiler/webdnn/graph/axis.py b/src/graph_transpiler/webdnn/graph/axis.py index 9870f4b70..02cf148aa 100644 --- a/src/graph_transpiler/webdnn/graph/axis.py +++ b/src/graph_transpiler/webdnn/graph/axis.py @@ -1,9 +1,32 @@ from enum import Enum, auto -# FIXME: DOCS class Axis(Enum): + """ + This class represents semantics of each dimension of variables. + + - :code:`Axis.N` + + number of samples (batch size), number of output channels in linear connection and convolution (number of filters). + + - :code:`Axis.C` + + number of features + + - :code:`Axis.H` + + height of image + + - :code:`Axis.W` + + width of image + + - :code:`Axis.T` + + length of series + """ N = auto() C = auto() H = auto() W = auto() + T = auto() diff --git a/src/graph_transpiler/webdnn/graph/converters/chainer.py b/src/graph_transpiler/webdnn/graph/converters/chainer.py deleted file mode 100644 index 6dabaaa6b..000000000 --- a/src/graph_transpiler/webdnn/graph/converters/chainer.py +++ /dev/null @@ -1,529 +0,0 @@ -# -*- coding:utf-8 -*- - -""" -Chainer Link -> Graph object converters -Assuming Chainer 1.23 -""" - -from typing import List, Tuple - -import chainer -import chainer.computational_graph -import numpy as np - -from webdnn.graph.axis import Axis -from webdnn.graph.graph import Graph -from webdnn.graph.operators.average_pooling_2d import AveragePooling2D -from webdnn.graph.operators.axiswise_bias import AxiswiseBias -from webdnn.graph.operators.axiswise_scale import AxiswiseScale -from webdnn.graph.operators.convolution2d import Convolution2D -from webdnn.graph.operators.deconvolution2d import Deconvolution2D -from webdnn.graph.operators.elementwise_sum import ElementwiseSum -from webdnn.graph.operators.elu import Elu -from webdnn.graph.operators.flatten import Flatten -from webdnn.graph.operators.linear import Linear -from webdnn.graph.operators.local_response_normalization import LocalResponseNormalization -from webdnn.graph.operators.max_pooling_2d import MaxPooling2D -from webdnn.graph.operators.relu import Relu -from webdnn.graph.operators.scalar_affine import ScalarAffine -from webdnn.graph.operators.tanh import Tanh -from webdnn.graph.order import OrderNC, OrderNCHW, OrderC, OrderCN, OrderHWNC, OrderHWCN, \ - OrderNHWC, OrderCNHW, \ - Order -from webdnn.graph.variable import Variable -from webdnn.graph.variables.attributes.input import Input -from webdnn.graph.variables.attributes.output import Output -from webdnn.graph.variables.constant_variable import ConstantVariable - -unique_ctr = 0 - - -def generate_unique_name(prefix): - global unique_ctr - unique_ctr += 1 - return prefix + str(unique_ctr) - - -class OperatorBlock: - # Chainerで1つのFunctionが2つのレイヤーに分解されたときに、その間をつなぐために生成されたVariable - hidden_vars: List[Variable] - # 元々のウェイトを扱いやすく変換したConstantを作成した場合に登録 - hidden_consts: List[ConstantVariable] - - def __init__(self): - self.hidden_vars = [] - self.hidden_consts = [] - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - raise NotImplementedError() - - -class ReluBlock(OperatorBlock): - cfunc: chainer.functions.ReLU - - def __init__(self, cfunc: chainer.functions.ReLU): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - assert len(inputs) == 1 - opr = Relu(generate_unique_name(self.cfunc.label)) - return opr(inputs[0]) - - -class EluBlock(OperatorBlock): - cfunc: chainer.functions.ELU - - def __init__(self, cfunc: chainer.functions.ELU): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - assert len(inputs) == 1 - opr = Elu(generate_unique_name(self.cfunc.label)) - return opr(inputs[0]) - - -class TanhBlock(OperatorBlock): - cfunc: chainer.functions.Tanh - - def __init__(self, cfunc: chainer.functions.Tanh): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - assert len(inputs) == 1 - opr = Tanh(generate_unique_name(self.cfunc.label)) - return opr(inputs[0]) - - -class LinearBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.connection.linear.LinearFunction - - def __init__(self, cfunc: chainer.functions.Linear): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - # noinspection PyUnresolvedReferences - linear_opr = Linear(generate_unique_name(self.cfunc.label)) - x = inputs[0] - w = inputs[1] - if x.ndim == 4 and w.ndim == 2: - # wを4次元に拡張 (NC -> NCHW) - x_shape_dict = x.shape_dict - w_shape_dict = w.shape_dict - assert x_shape_dict[Axis.C] * x_shape_dict[Axis.H] * x_shape_dict[Axis.W] == w_shape_dict[Axis.C] - assert w.order is OrderNC - w.order = OrderNCHW - w_new_shape = [w_shape_dict[Axis.N], x_shape_dict[Axis.C], x_shape_dict[Axis.H], - x_shape_dict[Axis.W]] - w.shape = w_new_shape - w.data = w.data.reshape(w_new_shape) - - opr_out, = linear_opr(inputs[0], inputs[1]) - if len(inputs) == 3: - # biasあり - # noinspection PyUnresolvedReferences - bias_opr = AxiswiseBias(generate_unique_name(self.cfunc.label), axis=Axis.C) - self.hidden_vars.append(opr_out) - opr_out, = bias_opr(opr_out, inputs[2]) - return opr_out, - - -class Convolution2DBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.connection.convolution_2d.Convolution2DFunction - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.connection.convolution_2d.Convolution2DFunction): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - w = inputs[1] - w_shape_dict = w.shape_dict - conv_opr = Convolution2D(generate_unique_name(self.cfunc.label), - ksize=(w_shape_dict[Axis.H], w_shape_dict[Axis.W]), - stride=(self.cfunc.sy, self.cfunc.sx), - padding=(self.cfunc.ph, self.cfunc.pw)) - - opr_out, = conv_opr(inputs[0], inputs[1]) - opr_out.change_order(OrderNCHW) - - if len(inputs) == 3: - # biasあり - bias_opr = AxiswiseBias(generate_unique_name(self.cfunc.label), axis=Axis.C) - self.hidden_vars.append(opr_out) - opr_out, = bias_opr(opr_out, inputs[2]) - return opr_out, - - -class Deconvolution2DBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.connection.deconvolution_2d.Deconvolution2DFunction - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.connection.deconvolution_2d.Deconvolution2DFunction): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - w = inputs[1] - w_shape_dict = w.shape_dict - conv_opr = Deconvolution2D(generate_unique_name(self.cfunc.label), - ksize=(w_shape_dict[Axis.H], w_shape_dict[Axis.W]), - stride=(self.cfunc.sy, self.cfunc.sx), - padding=(self.cfunc.ph, self.cfunc.pw)) - - opr_out, = conv_opr(inputs[0], inputs[1]) - opr_out.change_order(OrderNCHW) - - if len(inputs) == 3: - # biasあり - bias_opr = AxiswiseBias(generate_unique_name(self.cfunc.label), axis=Axis.C) - self.hidden_vars.append(opr_out) - opr_out, = bias_opr(opr_out, inputs[2]) - return opr_out, - - -class MaxPooling2DBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.pooling.max_pooling_2d.MaxPooling2D - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.pooling.max_pooling_2d.MaxPooling2D): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - conv_opr = MaxPooling2D(generate_unique_name(self.cfunc.label), - ksize=(self.cfunc.kh, self.cfunc.kw), - stride=(self.cfunc.sy, self.cfunc.sx), - padding=(self.cfunc.ph, self.cfunc.pw)) - - opr_out, = conv_opr(inputs[0]) - opr_out.change_order(OrderNCHW) - - return opr_out, - - -class AveragePooling2DBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.pooling.average_pooling_2d.AveragePooling2D - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.pooling.average_pooling_2d.AveragePooling2D): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - conv_opr = AveragePooling2D(generate_unique_name(self.cfunc.label), - ksize=(self.cfunc.kh, self.cfunc.kw), - stride=(self.cfunc.sy, self.cfunc.sx), - padding=(self.cfunc.ph, self.cfunc.pw)) - - opr_out, = conv_opr(inputs[0]) - opr_out.change_order(OrderNCHW) - - return opr_out, - - -class LocalResponseNormalizationBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.normalization.local_response_normalization.LocalResponseNormalization - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.normalization.local_response_normalization.LocalResponseNormalization): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - conv_opr = LocalResponseNormalization(generate_unique_name(self.cfunc.label), - n=self.cfunc.n, - k=self.cfunc.k, - alpha=self.cfunc.alpha, - beta=self.cfunc.beta) - - opr_out, = conv_opr(inputs[0]) - return opr_out, - - -class BatchNormalizationBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.normalization.batch_normalization.BatchNormalizationFunction - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.normalization.batch_normalization.BatchNormalizationFunction): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - assert len(inputs) == 5 - x, gamma, beta, mean, variance = inputs - # x以外の変数は、加工して新しいConstantとして使う - # (x - mean) / sqrt(var + eps) * gamma + beta - # gamma_div_std = gamma / sqrt(var + eps) - # beta_scaled = beta - mean * gamma_div_std - # y = x * gamma_div_std + beta_scaled - gamma_div_std = gamma.data / np.sqrt(variance.data + self.cfunc.eps) - beta_scaled = beta.data - mean.data * gamma_div_std - - scale_opr = AxiswiseScale(generate_unique_name(self.cfunc.label), axis=Axis.C) - gamma_div_std_const = ConstantVariable(gamma_div_std, OrderC) - scale_out, = scale_opr(x, gamma_div_std_const) - self.hidden_vars.append(scale_out) - self.hidden_consts.append(gamma_div_std_const) - - offset_opr = AxiswiseBias(generate_unique_name(self.cfunc.label), axis=Axis.C) - beta_scaled_const = ConstantVariable(beta_scaled, OrderC) - offset_out, = offset_opr(scale_out, beta_scaled_const) - self.hidden_consts.append(beta_scaled_const) - - return offset_out, - - -class AddBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.math.basic_math.Add - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.math.basic_math.Add): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> List[Variable]: - opr = ElementwiseSum(generate_unique_name(self.cfunc.label)) - return list(opr(*inputs)) - - -class AddConstantBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.math.basic_math.AddConstant - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.math.basic_math.AddConstant): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - opr = ScalarAffine(generate_unique_name(self.cfunc.label), scale=1, bias=float(self.cfunc.value)) - return opr(*inputs) - - -class MulConstantBlock(OperatorBlock): - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.math.basic_math.MulConstant - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.math.basic_math.MulConstant): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> Tuple[Variable]: - opr = ScalarAffine(generate_unique_name(self.cfunc.label), scale=float(self.cfunc.value), bias=0) - return opr(*inputs) - - -class ReshapeBlock(OperatorBlock): - # Currently, only removing HW axis is allowed - # NCHW (n,c,h,w) => NC (n,c*h*w) (assuming c-order) - # noinspection PyUnresolvedReferences - cfunc: chainer.functions.array.reshape.Reshape - - # noinspection PyUnresolvedReferences - def __init__(self, cfunc: chainer.functions.array.reshape.Reshape): - super().__init__() - self.cfunc = cfunc - - def __call__(self, inputs: List[Variable]) -> List[Variable]: - assert len(inputs) == 1 - # NCHWをNCにする場合のみ想定 - assert inputs[0].order is OrderNCHW - assert len(self.cfunc.shape) == 2 - assert self.cfunc.shape[0] == inputs[0].shape[0] # Nは変化しない - - opr = Flatten(generate_unique_name(self.cfunc.label), in_axes=[Axis.C, Axis.H, Axis.W], out_axis=Axis.C) - return list(opr(inputs[0])) - - -# noinspection PyUnresolvedReferences -BLOCK_CLASSES = [(chainer.functions.ReLU, ReluBlock), - (chainer.functions.ELU, EluBlock), - (chainer.functions.Tanh, TanhBlock), - (chainer.functions.connection.linear.LinearFunction, LinearBlock), - (chainer.functions.connection.convolution_2d.Convolution2DFunction, Convolution2DBlock), - (chainer.functions.connection.deconvolution_2d.Deconvolution2DFunction, Deconvolution2DBlock), - (chainer.functions.pooling.max_pooling_2d.MaxPooling2D, MaxPooling2DBlock), - (chainer.functions.pooling.average_pooling_2d.AveragePooling2D, AveragePooling2DBlock), - (chainer.functions.normalization.batch_normalization.BatchNormalizationFunction, - BatchNormalizationBlock), - (chainer.functions.math.basic_math.Add, AddBlock), - (chainer.functions.math.basic_math.AddConstant, AddConstantBlock), - (chainer.functions.math.basic_math.MulConstant, MulConstantBlock), - (chainer.functions.array.reshape.Reshape, ReshapeBlock), - (chainer.functions.normalization.local_response_normalization.LocalResponseNormalization, - LocalResponseNormalizationBlock)] - - -class ChainerGraphConverter: - def __init__(self): - self._cvar_to_nvar = {} # id(chainer.Variable) => Variable (note: chainerに対応しないVariableも作られる) - self._cvar_ids = [] # id(chainer.Variable) - self._known_nvars = [] # 存在するVariable(include Constant) - - def convert_from_inout_vars(self, inputs: List[chainer.Variable], outputs: List[chainer.Variable]): - chainer_graph = chainer.computational_graph.build_computational_graph(outputs) - return self.convert(chainer_graph, inputs, outputs) - - def convert(self, chainer_computational_graph: chainer.computational_graph.ComputationalGraph, - input_vars: List[chainer.Variable], output_vars: List[chainer.Variable]) -> Graph: - # 戦略 - # 生成済み変数(chainer.Variable)をセットに入れる; 入力変数およびウェイト - # 生成済み変数だけを入力とし、未処理のchainer.Functionを変換し、生成済み変数セットに追加 - # 未処理のchainer.Functionがなくなったら終わり - self._cvar_to_nvar = {} - self._cvar_ids = [] - self._known_nvars = [] - self._convert_weight_vars(chainer_computational_graph) - self._convert_input_vars(input_vars) - - pending_functions = [cfunc for cfunc in chainer_computational_graph.nodes if - isinstance(cfunc, chainer.Function)] - while len(pending_functions) > 0: - for cfunc in pending_functions: - if all(((id(cvar) in self._cvar_ids) for cvar in cfunc.inputs)): - # このレイヤーは入力が揃った - opr_block = self._construct_operator_block(cfunc) - out_nvars = opr_block([self._cvar_to_nvar[id(cvar)] for cvar in cfunc.inputs]) - assert len(out_nvars) == len(cfunc.outputs), str(cfunc) - self._known_nvars.extend(opr_block.hidden_consts) - self._known_nvars.extend(opr_block.hidden_vars) - # 出力変数を対応づける - for out_nvar, out_cvar_wref in zip(out_nvars, cfunc.outputs): - out_cvar = out_cvar_wref() - assert tuple(out_nvar.shape) == out_cvar.shape, str(cfunc) - self._cvar_to_nvar[id(out_cvar)] = out_nvar - self._cvar_ids.append(id(out_cvar)) - self._known_nvars.append(out_nvar) - pending_functions.remove(cfunc) - break # for cfunc in pending_functions - else: - print(pending_functions) - raise ValueError("inputs to functions cannot be resolved.") - - # 出力変数にAttributeをつける - for cvar in output_vars: - if id(cvar) not in self._cvar_ids: - raise ValueError("Output variable is not generated by graph.") - nvar = self._cvar_to_nvar[id(cvar)] - nvar.attributes.add(Output) - - # このフレームワークで標準的なデータオーダーに変更 - self._transpose_vars(self._known_nvars) - - return Graph([self._cvar_to_nvar[id(cvar)] for cvar in input_vars], - [self._cvar_to_nvar[id(cvar)] for cvar in output_vars]) - - def _convert_input_vars(self, input_vars: List[chainer.Variable]): - for cvar in input_vars: - nvar = self._convert_var(cvar) - nvar.attributes.add(Input(nvar)) - - def _convert_weight_vars(self, chainer_computational_graph: chainer.computational_graph.ComputationalGraph): - # 名前付きの変数を変換 - - # Phase1. 特殊ケースのみ先に処理 - for cvar in chainer_computational_graph.nodes: - # noinspection PyUnresolvedReferences - if isinstance(cvar, chainer.functions.normalization.batch_normalization.BatchNormalizationFunction): - # BNのmean, varは名無しだがウェイト - assert len(cvar.inputs) == 5 # data, gamma, bias, mean, var - self._convert_var(cvar.inputs[3], force_constant=True) - self._convert_var(cvar.inputs[4], force_constant=True) - - elif isinstance(cvar, chainer.functions.connection.deconvolution_2d.Deconvolution2DFunction): - # chainerのDeconvolution2DのWは(Cin, Cout, Kh, Kw)のオーダー - self._convert_var(cvar.inputs[1], force_constant=True, force_order=OrderCNHW) - - # Phase2. 残りを処理 - for cvar in chainer_computational_graph.nodes: - # noinspection PyUnresolvedReferences - if isinstance(cvar, chainer.Variable): - if id(cvar) not in self._cvar_ids and cvar.name is not None: - self._convert_var(cvar) - - def _convert_var(self, - cvar: chainer.Variable, - force_constant=False, - force_order: Order = None): - assert id(cvar) not in self._cvar_ids - ndim = len(cvar.shape) - if force_order: - order = force_order - - else: - if ndim == 4: - # both weight and variable - order = OrderNCHW - elif ndim == 2: - # both weight and variable - order = OrderNC - elif ndim == 1: - # both weight and variable - order = OrderC - else: - raise NotImplementedError() - - assert order.ndim == ndim - - if cvar.name is not None or force_constant: - nvar = ConstantVariable(chainer.cuda.to_cpu(cvar.data), order) # force on CPU - else: - nvar = Variable(cvar.shape, order) - - self._cvar_to_nvar[id(cvar)] = nvar - self._cvar_ids.append(id(cvar)) - self._known_nvars.append(nvar) - return nvar - - # noinspection PyMethodMayBeStatic - def _construct_operator_block(self, cfunc: chainer.Function) -> OperatorBlock: - for function_class, block_class in BLOCK_CLASSES: - if isinstance(cfunc, function_class): - return block_class(cfunc) - raise ValueError("Unknown layer {}".format(type(cfunc))) - - # noinspection PyMethodMayBeStatic - def _transpose_vars(self, nvars: List[Variable]): - """ - 変数を、標準的なAxisOrderに変更 - :param nvars: - :return: - """ - for nvar in nvars: - if isinstance(nvar, ConstantVariable): - if nvar.ndim == 1: - nvar.change_order(OrderC) - elif nvar.ndim == 2: - nvar.change_order(OrderCN) - elif nvar.ndim == 4: - assert len(nvar.input_to) == 1 - first_input_to = list(nvar.input_to)[0] - if isinstance(first_input_to, Convolution2D): - nvar.change_order(OrderHWNC) - elif isinstance(first_input_to, Deconvolution2D): - nvar.change_order(OrderHWNC) - elif isinstance(first_input_to, Linear): - nvar.change_order(OrderHWCN) - else: - # 今の所ないはず - raise ValueError() - else: - if nvar.ndim == 1: - nvar.change_order(OrderC) - elif nvar.ndim == 2: - nvar.change_order(OrderNC) - elif nvar.ndim == 4: - nvar.change_order(OrderNHWC) diff --git a/src/graph_transpiler/webdnn/graph/converters/keras.py b/src/graph_transpiler/webdnn/graph/converters/keras.py deleted file mode 100644 index fd6eb2e73..000000000 --- a/src/graph_transpiler/webdnn/graph/converters/keras.py +++ /dev/null @@ -1,572 +0,0 @@ -# -*- coding:utf-8 -*- - -""" -Keras model -> Graph object converters -Assuming Keras 2.0.4 - -Currently, the system assumes the model is trained with "data_format" == "channels_last". -If this is not the case, Flatten layer which follows Convolution have to change the order of variable. -Convolution implementation is currently assuming variable is NHWC. -""" - -from typing import List, Tuple, Dict - -import json -from warnings import warn - -import numpy as np -import h5py - -from webdnn.graph.axis import Axis -from webdnn.graph.graph import Graph -from webdnn.graph.operator import Operator -from webdnn.graph.operators.average_pooling_2d import AveragePooling2D -from webdnn.graph.operators.axiswise_bias import AxiswiseBias -from webdnn.graph.operators.axiswise_scale import AxiswiseScale -from webdnn.graph.operators.concat import Concat -from webdnn.graph.operators.convolution2d import Convolution2D -from webdnn.graph.operators.deconvolution2d import Deconvolution2D -from webdnn.graph.operators.elementwise_sum import ElementwiseSum -from webdnn.graph.operators.elu import Elu -from webdnn.graph.operators.flatten import Flatten -from webdnn.graph.operators.linear import Linear -from webdnn.graph.operators.local_response_normalization import LocalResponseNormalization -from webdnn.graph.operators.max_pooling_2d import MaxPooling2D -from webdnn.graph.operators.relu import Relu -from webdnn.graph.operators.scalar_affine import ScalarAffine -from webdnn.graph.operators.tanh import Tanh -from webdnn.graph.order import OrderNC, OrderNCHW, OrderC, OrderCN, OrderHWNC, OrderHWCN, \ - OrderNHWC, OrderCNHW, OrderCHWN, Order -from webdnn.graph.variable import Variable -from webdnn.graph.variables.attributes.input import Input -from webdnn.graph.variables.attributes.output import Output -from webdnn.graph.variables.constant_variable import ConstantVariable - - -class CommonGraphConverter: - model_config: Dict[str, object] - weights: h5py.Group - - def __init__(self, model_config: Dict[str, object], weights: h5py.Group): - self.model_config = model_config - self.weights = weights - - def convert(self, input_shapes: List[List[int]]) -> Graph: - pass - - def convert_layer(self, layer_class_name: str, layer_config: Dict[str, object], inputs: List[Variable]) -> List[ - Variable]: - outputs = None - if layer_class_name == "Dense": - outputs = self.convert_layer_dense(layer_config, inputs) - elif layer_class_name == "Conv2D": - outputs = self.convert_layer_conv2d(layer_config, inputs) - elif layer_class_name == "MaxPooling2D": - outputs = self.convert_layer_maxpooling2d(layer_config, inputs) - elif layer_class_name == "AveragePooling2D": - outputs = self.convert_layer_averagepooling2d(layer_config, inputs) - elif layer_class_name == "GlobalAveragePooling2D": - outputs = self.convert_layer_globalaveragepooling2d(layer_config, inputs) - elif layer_class_name == "BatchNormalization": - outputs = self.convert_layer_batchnormalization(layer_config, inputs) - elif layer_class_name == "Flatten": - outputs = self.convert_layer_flatten(layer_config, inputs) - elif layer_class_name == "Concatenate": - outputs = self.convert_layer_concatenate(layer_config, inputs) - elif layer_class_name == "Add": - outputs = self.convert_layer_add(layer_config, inputs) - elif layer_class_name == "Activation": - outputs = self.convert_layer_activation(layer_config, inputs) - elif layer_class_name == "Dropout": - warn("Omitting Dropout layer") - outputs = inputs - else: - raise NotImplementedError(f"Unknown Layer {layer_class_name}") - return outputs - - def convert_layer_dense(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[Variable]: - assert len(inputs) == 1 - input = inputs[0] - name: str = layer_config["name"] - weight_array = self.weights[f"{name}/{name}/kernel:0"].value - weight_var = ConstantVariable(weight_array, OrderCN) # shape: (in, out) - linear_opr = Linear(name) - y, = linear_opr(input, weight_var) - - if layer_config["use_bias"]: - bias_array = self.weights[f"{name}/{name}/bias:0"].value - bias_var = ConstantVariable(bias_array, OrderC) - bias_opr = AxiswiseBias(name + "_bias", Axis.C) - y, = bias_opr(y, bias_var) - - act_opr: Operator = None - activation_type: str = layer_config["activation"] - if activation_type == "relu": - act_opr = Relu(name + "_activation") - elif activation_type == "softmax": - warn("omitting softmax activation") - else: - raise NotImplementedError(f"Unknown activation {activation_type}") - - if act_opr is not None: - y, = act_opr(y) - - return [y] - - def convert_layer_conv2d(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[Variable]: - """ - Example: - {'class_name': 'Conv2D', - 'config': {'activation': 'relu', - 'activity_regularizer': None, - 'bias_constraint': None, - 'bias_initializer': {'class_name': 'Zeros', 'config': {}}, - 'bias_regularizer': None, - 'data_format': 'channels_last', - 'dilation_rate': [1, 1], - 'filters': 64, - 'kernel_constraint': None, - 'kernel_initializer': {'class_name': 'VarianceScaling', - 'config': {'distribution': 'uniform', - 'mode': 'fan_avg', - 'scale': 1.0, - 'seed': None}}, - 'kernel_regularizer': None, - 'kernel_size': [3, 3], - 'name': 'conv2d_2', - 'padding': 'valid', - 'strides': [1, 1], - 'trainable': True, - 'use_bias': True}}, - :param layer_config: - :param inputs: - :return: - """ - assert len(inputs) == 1 - input = inputs[0] - name: str = layer_config["name"] - weight_array = self.weights[f"{name}/{name}/kernel:0"].value - assert layer_config["data_format"] == "channels_last" - weight_var = ConstantVariable(weight_array, OrderHWCN) # order does not depend on data_format - ksize: Tuple[int, int] = tuple(layer_config["kernel_size"]) - stride: Tuple[int, int] = tuple(layer_config["strides"]) - padding_keras: str = layer_config["padding"] # valid or same - if isinstance(padding_keras, tuple): - # preprocess_zeropadding2d - padding = padding_keras - elif padding_keras == "valid": - padding = (0, 0) - elif padding_keras == "same": - padding = (ksize[0] // 2, ksize[1] // 2) - else: - raise ValueError("Unknown padding") - - conv2d_opr = Convolution2D(name, - ksize=ksize, - stride=stride, - padding=padding) - y, = conv2d_opr(input, weight_var) - - if layer_config["use_bias"]: - bias_array = self.weights[f"{name}/{name}/bias:0"].value - bias_var = ConstantVariable(bias_array, OrderC) - bias_opr = AxiswiseBias(name + "_bias", Axis.C) - y, = bias_opr(y, bias_var) - - act_opr: Operator = None - activation_type: str = layer_config["activation"] - if activation_type == "relu": - act_opr = Relu(name + "_activation") - elif activation_type == "softmax": - warn("omitting softmax activation") - elif activation_type == "linear": - pass - else: - raise NotImplementedError(f"Unknown activation {activation_type}") - - if act_opr is not None: - y, = act_opr(y) - - return [y] - - def convert_layer_maxpooling2d(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[Variable]: - """ - Example: - {'class_name': 'MaxPooling2D', - 'config': {'data_format': 'channels_last', - 'name': 'max_pooling2d_1', - 'padding': 'valid', - 'pool_size': [2, 2], - 'strides': [2, 2], - 'trainable': True}}, - - :param layer_config: - :param inputs: - :return: - """ - assert len(inputs) == 1 - input = inputs[0] - name: str = layer_config["name"] - ksize: Tuple[int, int] = tuple(layer_config["pool_size"]) - stride: Tuple[int, int] = tuple(layer_config["strides"]) - padding_keras: str = layer_config["padding"] # valid or same - if padding_keras == "valid": - padding = (0, 0) - elif padding_keras == "same": - padding = (ksize[0] // 2, ksize[1] // 2) - else: - raise ValueError("Unknown padding") - - max_pooling_2d_opr = MaxPooling2D(name, - ksize=ksize, - stride=stride, - padding=padding) - y, = max_pooling_2d_opr(input) - - return [y] - - def convert_layer_batchnormalization(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[ - Variable]: - """ - Example: - {'class_name': 'BatchNormalization', - 'config': {'axis': 3, - 'beta_constraint': None, - 'beta_initializer': {'class_name': 'Zeros', 'config': {}}, - 'beta_regularizer': None, - 'center': True, - 'epsilon': 0.001, - 'gamma_constraint': None, - 'gamma_initializer': {'class_name': 'Ones', 'config': {}}, - 'gamma_regularizer': None, - 'momentum': 0.99, - 'moving_mean_initializer': {'class_name': 'Zeros', 'config': {}}, - 'moving_variance_initializer': {'class_name': 'Ones', 'config': {}}, - 'name': 'bn2a_branch2a', - 'scale': True, - 'trainable': True}, - 'inbound_nodes': [[['res2a_branch2a', 0, 0, {}]]], - 'name': 'bn2a_branch2a'}, - - :param layer_config: - :param inputs: - :return: - """ - assert len(inputs) == 1 - input = inputs[0] - name: str = layer_config["name"] - - axis = input.order.axes[layer_config["axis"]] - - mean = self.weights[f"{name}/{name}/moving_mean:0"].value - variance = self.weights[f"{name}/{name}/moving_variance:0"].value - - if layer_config["scale"]: - gamma = self.weights[f"{name}/{name}/gamma:0"].value - else: - gamma = np.ones_like(variance) - - if layer_config["center"]: - beta = self.weights[f"{name}/{name}/beta:0"].value - else: - beta = np.zeros_like(mean) - - # (x - mean) / sqrt(var + eps) * gamma + beta - # gamma_div_std = gamma / sqrt(var + eps) - # beta_scaled = beta - mean * gamma_div_std - # y = x * gamma_div_std + beta_scaled - - gamma_div_std = gamma / np.sqrt(variance + layer_config["epsilon"]) - beta_scaled = beta - mean * gamma_div_std - - scale_opr = AxiswiseScale(name + "_scale", axis=axis) - bias_opr = AxiswiseBias(name + "_bias", axis=axis) - scale_out, = scale_opr(input, ConstantVariable(gamma_div_std, OrderC)) - y, = bias_opr(scale_out, ConstantVariable(beta_scaled, OrderC)) - - return [y] - - def convert_layer_averagepooling2d(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[ - Variable]: - """ - Example: - {'class_name': 'AveragePooling2D', - 'config': {'data_format': 'channels_last', - 'name': 'avg_pool', - 'padding': 'valid', - 'pool_size': [7, 7], - 'strides': [7, 7], - 'trainable': True}, - 'inbound_nodes': [[['activation_49', 0, 0, {}]]], - 'name': 'avg_pool'}, - - :param layer_config: - :param inputs: - :return: - """ - assert len(inputs) == 1 - input = inputs[0] - name: str = layer_config["name"] - ksize: Tuple[int, int] = tuple(layer_config["pool_size"]) - stride: Tuple[int, int] = tuple(layer_config["strides"]) - padding_keras: str = layer_config["padding"] # valid or same - if padding_keras == "valid": - padding = (0, 0) - elif padding_keras == "same": - padding = (ksize[0] // 2, ksize[1] // 2) - else: - raise ValueError("Unknown padding") - # ksize: Tuple[int, int] = (input.shape_dict[Axis.H], input.shape_dict[Axis.W]) # FIXME: is this need? - average_pooling_2d_opr = AveragePooling2D(name, - ksize=ksize, - stride=stride, - padding=padding) - y, = average_pooling_2d_opr(input) - - return [y] - - def convert_layer_globalaveragepooling2d(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[ - Variable]: - """ - Example: - {'class_name': 'GlobalAveragePooling2D', - 'config': {'data_format': 'channels_last', - 'name': 'global_average_pooling2d_1', - 'trainable': True}, - 'inbound_nodes': [[['add_2', 0, 0, {}]]], - 'name': 'global_average_pooling2d_1'}, - - :param layer_config: - :param inputs: - :return: - """ - assert len(inputs) == 1 - input = inputs[0] - name: str = layer_config["name"] - ksize: Tuple[int, int] = (input.shape_dict[Axis.H], input.shape_dict[Axis.W]) - - average_pooling_2d_opr = AveragePooling2D(name, - ksize=ksize, - stride=(1, 1), - padding=(0, 0)) - y, = average_pooling_2d_opr(input) - - # データ順序を変えずに2Dにする - in_axes = y.order.axes.copy() - assert in_axes[0] == Axis.N - in_axes.remove(Axis.N) - flatten_opr = Flatten(name + "_flatten", in_axes=in_axes, out_axis=Axis.C) - y, = flatten_opr(y) - - return [y] - - def convert_layer_flatten(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[Variable]: - """ - Example: - {'class_name': 'Flatten', - 'config': {'name': 'flatten_1', 'trainable': True}}, - - :param layer_config: - :param inputs: - :return: - """ - assert len(inputs) == 1 - input = inputs[0] - name: str = layer_config["name"] - - # データ順序を変えずに2Dにするだけ - in_axes = input.order.axes.copy() - assert in_axes[0] == Axis.N - in_axes.remove(Axis.N) - flatten_opr = Flatten(name, in_axes=in_axes, out_axis=Axis.C) - y, = flatten_opr(input) - - return [y] - - def convert_layer_concatenate(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[Variable]: - """ - Example: - {'name': 'mixed0', 'trainable': True, 'axis': 3} - - :param layer_config: - :param inputs: - :return: - """ - name: str = layer_config["name"] - - axis = inputs[0].order.axes[layer_config["axis"]] - concat_opr = Concat(name, axis=axis) - y, = concat_opr(*inputs) - - return [y] - - def convert_layer_add(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[Variable]: - """ - Example: - {'class_name': 'Add', - 'config': {'name': 'add_1', 'trainable': True}, - 'inbound_nodes': [[['conv2d_2', 0, 0, {}], ['conv2d_3', 0, 0, {}]]], - 'name': 'add_1'}, - :param layer_config: - :param inputs: - :return: - """ - name: str = layer_config["name"] - - sum_opr = ElementwiseSum(name) - y, = sum_opr(*inputs) - - return [y] - - def convert_layer_activation(self, layer_config: Dict[str, object], inputs: List[Variable]) -> List[Variable]: - """ - Example: - {'class_name': 'Activation', - 'config': {'activation': 'relu', 'name': 'activation_2', 'trainable': True}, - 'inbound_nodes': [[['bn2a_branch2a', 0, 0, {}]]], - 'name': 'activation_2'}, - :param layer_config: - :param inputs: - :return: - """ - assert len(inputs) == 1 - input = inputs[0] - name: str = layer_config["name"] - - act_opr: Operator = None - activation_type: str = layer_config["activation"] - if activation_type == "relu": - act_opr = Relu(name + "_activation") - else: - raise NotImplementedError(f"Unknown activation {activation_type}") - - y, = act_opr(input) - - return [y] - - -class SequentialGraphConverter(CommonGraphConverter): - def __init__(self, model_config: Dict[str, object], weights: h5py.Group): - super().__init__(model_config, weights) - - def convert(self, input_shapes: List[List[int]]) -> Graph: - graph_inputs = [] - for input_shape in input_shapes: - order = None - if len(input_shape) == 1: - order = OrderC - elif len(input_shape) == 2: - order = OrderNC - elif len(input_shape) == 4: - # Assuming data_format == "channels_last": - order = OrderNHWC - else: - raise NotImplementedError("Input shape must be 1,2,4 dimensions") - v = Variable(input_shape, order) - graph_inputs.append(v) - - current_vars = graph_inputs - for layer in self.model_config["config"]: - current_vars = self.convert_layer(layer["class_name"], layer["config"], current_vars) - graph_outputs = current_vars - - return Graph(graph_inputs, graph_outputs) - - -class ModelGraphConverter(CommonGraphConverter): - def __init__(self, model_config: Dict[str, object], weights: h5py.Group): - super().__init__(model_config, weights) - - def convert(self, input_shapes: List[List[int]]) -> Graph: - input_layers = self.model_config["config"]["input_layers"] # [['input_1', 0, 0]] - self.preprocess_zeropadding2d() - # Variableは(layer_name, 0, 0)という形式のキーで管理 - var_dict = {} - - graph_inputs = [] - for input_layer, input_shape in zip(input_layers, input_shapes): - order = None - if len(input_shape) == 1: - order = OrderC - elif len(input_shape) == 2: - order = OrderNC - elif len(input_shape) == 4: - # Assuming data_format == "channels_last": - order = OrderNHWC - else: - raise NotImplementedError("Input shape must be 1,2,4 dimensions") - v = Variable(input_shape, order) - - graph_inputs.append(v) - var_dict[tuple(input_layer)] = v # key: ('input_1', 0, 0) - - for layer in self.model_config["config"]["layers"]: - layer_class_name = layer["class_name"] - if layer_class_name == "InputLayer": - # 入力を表すダミーレイヤー - continue - # 入力変数をリストにまとめる - input_variables = [] - assert len(layer["inbound_nodes"]) == 1 # [[var1, var2, ...]] - for inbound_node in layer["inbound_nodes"][0]: - key = (inbound_node[0], inbound_node[1], inbound_node[2]) - assert inbound_node[3] == {} - input_variables.append(var_dict[key]) - - output_variables = self.convert_layer(layer_class_name, layer["config"], input_variables) - assert len(output_variables) == 1 # 複数出力の時の表現を認識できない - key = (layer["name"], 0, 0) - assert key not in var_dict - var_dict[key] = output_variables[0] - - output_layers = self.model_config["config"]["output_layers"] - graph_outputs = [] - for output_layer in output_layers: - graph_outputs.append(var_dict[tuple(output_layer)]) - - return Graph(graph_inputs, graph_outputs) - - def preprocess_zeropadding2d(self): - """ - ZeroPadding2D -> Conv2D のパターンについて、Conv2Dのpaddingに統合してZeroPadding2D layerを消去する - :return: - """ - - zeropad_layers = dict() # レイヤーの出力変数名とレイヤー情報 - for layer in self.model_config["config"]["layers"]: - layer_class_name = layer["class_name"] - if layer_class_name == "ZeroPadding2D": - output_key = (layer["name"], 0, 0) - zeropad_layers[output_key] = layer - elif layer_class_name == "Conv2D": - # 自身の入力がZeroPaddingの入力ならば対応する - input_key = tuple(layer["inbound_nodes"][0][0][:3]) - if input_key in zeropad_layers: - pre_zeropad_layer = zeropad_layers[input_key] - padding_raw = pre_zeropad_layer["config"]["padding"] # [[top, bottom], [left, right]] - assert padding_raw[0][0] == padding_raw[0][1] - assert padding_raw[1][0] == padding_raw[1][1] - assert layer["config"]["padding"] == "valid" - # Conv2Dのpaddingのところに代入し、Conv2Dレイヤーの変換時に利用 - layer["config"]["padding"] = (padding_raw[0][0], padding_raw[1][0]) - # zeropadding layerの入力をconv自体の入力に置き換える - layer["inbound_nodes"] = pre_zeropad_layer["inbound_nodes"] - - for layer in zeropad_layers.values(): - self.model_config["config"]["layers"].remove(layer) - - -class KerasGraphConverter: - def __init__(self): - pass - - def convert(self, model: h5py.File, input_shapes: List[List[int]]) -> Graph: - model_config = json.loads(model.attrs["model_config"]) - if model_config["class_name"] == "Sequential": - converter = SequentialGraphConverter(model_config, model["model_weights"]) - elif model_config["class_name"] == "Model": - converter = ModelGraphConverter(model_config, model["model_weights"]) - else: - raise NotImplementedError("Non-sequential model is currently not implemented") - - return converter.convert(input_shapes) diff --git a/src/graph_transpiler/webdnn/graph/graph.py b/src/graph_transpiler/webdnn/graph/graph.py index 17295a464..653816fd6 100644 --- a/src/graph_transpiler/webdnn/graph/graph.py +++ b/src/graph_transpiler/webdnn/graph/graph.py @@ -4,8 +4,16 @@ WEBDNN_LICENSE = "(C) Machine Intelligence Laboratory (The University of Tokyo), MIT License" -# FIXME: DOCS + class Graph: + """ + Graph is computation graph of DNN model. + + Args: + inputs (list of :class:`~webdnn.graph.variable.Variable`): input variables + outputs (list of :class:`~webdnn.graph.variable.Variable`): output variables + """ + def __init__(self, inputs: Iterable[Variable], outputs: Iterable[Variable]): diff --git a/src/graph_transpiler/webdnn/graph/interface.py b/src/graph_transpiler/webdnn/graph/interface.py deleted file mode 100644 index 7212af066..000000000 --- a/src/graph_transpiler/webdnn/graph/interface.py +++ /dev/null @@ -1,121 +0,0 @@ -from abc import ABCMeta -from typing import List, Type, Dict, Set, Tuple, Optional - -from webdnn.graph.order import Order - - -class IAttribute: - node: "INode" - - -class INode: - attributes: Set[IAttribute] - parameters: Dict[str, any] - prevs: Set["INode"] - nexts: Set["INode"] - - # noinspection PyUnusedLocal - def __init__(self, name: Optional[str]): - raise NotImplementedError - - def append_prev(self, prev: "INode"): - raise NotImplementedError - - def remove_prev(self, prev: "INode"): - raise NotImplementedError - - def append_next(self, next: "INode"): - raise NotImplementedError - - def remove_next(self, next: "INode"): - raise NotImplementedError - - def __repr__(self): - raise NotImplementedError - - def __str__(self): - raise NotImplementedError - - -class IVariable(INode, metaclass=ABCMeta): - shape: List[int] - input_to: Set["IOperator"] - output_from: "IOperator" - order: Order - - # noinspection PyUnusedLocal,PyMissingConstructor - def __init__(self, shape: List[int], order: Order): - raise NotImplementedError - - @property - def name(self) -> str: - raise NotImplementedError - - @name.setter - def name(self, name: str) -> None: - raise NotImplementedError - - @property - def size(self) -> int: - raise NotImplementedError - - @property - def ndim(self) -> int: - raise NotImplementedError - - @property - def shape_dict(self): - raise NotImplementedError - - def change_order(self, order: Order) -> None: - raise NotImplementedError - - def __repr__(self) -> str: - raise NotImplementedError - - def __str__(self) -> str: - raise NotImplementedError - - -class IOperator(INode, metaclass=ABCMeta): - inputs: Dict[str, IVariable] - outputs: Dict[str, IVariable] - - def get_input_name(self, var: IVariable) -> None: - raise NotImplementedError - - def get_output_name(self, var: IVariable) -> str: - raise NotImplementedError - - def append_input(self, name: str, var: IVariable) -> None: - raise NotImplementedError - - def remove_input(self, var: IVariable) -> None: - raise NotImplementedError - - def replace_input(self, v_old: IVariable, v_new: IVariable) -> None: - raise NotImplementedError - - def append_output(self, name: str, var: IVariable) -> None: - raise NotImplementedError - - def remove_output(self, var: IVariable) -> None: - raise NotImplementedError - - def replace_output(self, v_old: IVariable, v_new: IVariable) -> None: - raise NotImplementedError - - def remove_all(self) -> None: - raise NotImplementedError - - def __repr__(self) -> str: - raise NotImplementedError - - def __str__(self) -> str: - raise NotImplementedError - - def __call__(self, *args, **kwargs) -> Tuple[IVariable]: - raise NotImplementedError - - def get_attribute(self, Attr: Type[IAttribute]) -> List[IAttribute]: - raise NotImplementedError diff --git a/src/graph_transpiler/webdnn/graph/node.py b/src/graph_transpiler/webdnn/graph/node.py index b5d9637a4..cbd51033c 100644 --- a/src/graph_transpiler/webdnn/graph/node.py +++ b/src/graph_transpiler/webdnn/graph/node.py @@ -1,6 +1,6 @@ from typing import Dict, Set, Type, Optional -from webdnn.graph.interface import IAttribute, INode +from webdnn.graph import attribute _node_serial_counter_dict: Dict[Type["Node"], int] = {} @@ -15,8 +15,11 @@ def _generate_name(node: "Node"): return name -class Node(INode): - attributes: Set[IAttribute] +class Node: + """ + Basic graph node class. + """ + attributes: Set["attribute.Attribute"] parameters: Dict[str, any] prevs: Set["Node"] nexts: Set["Node"] diff --git a/src/graph_transpiler/webdnn/graph/operator.py b/src/graph_transpiler/webdnn/graph/operator.py index adbb089f0..18432556e 100644 --- a/src/graph_transpiler/webdnn/graph/operator.py +++ b/src/graph_transpiler/webdnn/graph/operator.py @@ -1,18 +1,30 @@ from typing import Dict, Tuple, Type, List +from webdnn.graph import variable from webdnn.graph.attribute import Attribute -from webdnn.graph.interface import IVariable, IOperator from webdnn.graph.node import Node -# FIXME: DOCS -class Operator(Node, IOperator): +class Operator(Node): + """ + Operator a.k.a layer or function, is one of key component in DNN computation graph. + + Args: + name (str): the name + + Attributes: + inputs (dict of :class:`~webdnn.graph.variable.Variable`): input varibales + outputs (dict of :class:`~webdnn.graph.variable.Variable`): output variable + """ + inputs: Dict[str, "variable.Variable"] + outputs: Dict[str, "variable.Variable"] + def __init__(self, name: str): super().__init__(name) - self.inputs: Dict[str, IVariable] = {} - self.outputs: Dict[str, IVariable] = {} + self.inputs = {} + self.outputs = {} - def get_input_name(self, var: IVariable): + def _get_input_name(self, var: "variable.Variable"): for name, v in self.inputs.items(): if v is not var: continue @@ -22,7 +34,7 @@ def get_input_name(self, var: IVariable): else: raise KeyError(f"'{name}' is not input of {self}") - def get_output_name(self, var: IVariable): + def _get_output_name(self, var: "variable.Variable"): for name, v in self.outputs.items(): if v is not var: continue @@ -32,9 +44,13 @@ def get_output_name(self, var: IVariable): else: raise KeyError(f"'{name}' is not output of {self}") - def append_input(self, name: str, var: IVariable): + def append_input(self, name: str, var: "variable.Variable"): """ - 入力変数を追加する + Append input variable + + Args: + name(str): the name of input variable + var(:class:`~webdnn.graph.variable.Variable`): the variable """ # noinspection PyTypeChecker self.append_prev(var) @@ -42,20 +58,27 @@ def append_input(self, name: str, var: IVariable): self.inputs[name] = var var.input_to.add(self) - def remove_input(self, var: IVariable): + def remove_input(self, var: "variable.Variable"): """ - 入力変数を解除する + Remove input variable + + Args: + var(:class:`~webdnn.graph.variable.Variable`): the variable """ # noinspection PyTypeChecker self.remove_prev(var) - name = self.get_input_name(var) + name = self._get_input_name(var) self.inputs.pop(name) var.input_to.remove(self) - def replace_input(self, v_old: IVariable, v_new: IVariable): + def replace_input(self, v_old: "variable.Variable", v_new: "variable.Variable"): """ - 入力変数を置き換える + Replace input variable with other variable + + Args: + v_old(:class:`~webdnn.graph.variable.Variable`): the variable which is removed + v_new(:class:`~webdnn.graph.variable.Variable`): the variable which is appended """ assert v_old.ndim == v_new.ndim, \ "[operator.replace_input(v_old, v_new)] v_old and v_new must have same number of dimensions." + \ @@ -67,13 +90,17 @@ def replace_input(self, v_old: IVariable, v_new: IVariable): "[operator.replace_input(v_old, v_new)] v_old and v_new must be same shape." + \ f"actual: v_old.order = {v_old.shape}, v_new.order = {v_new.shape}" - name = self.get_input_name(v_old) + name = self._get_input_name(v_old) self.remove_input(v_old) self.append_input(name, v_new) - def append_output(self, name: str, var: IVariable): + def append_output(self, name: str, var: "variable.Variable"): """ - 出力変数を追加する + Append output variable + + Args: + name(str): the name of output variable + var(:class:`~webdnn.graph.variable.Variable`): the variable """ if var.output_from is not None: raise KeyError(f"{var} has been registered as f{var.output_from}'s output already.") @@ -84,20 +111,27 @@ def append_output(self, name: str, var: IVariable): self.outputs[name] = var var.output_from = self - def remove_output(self, var: IVariable): + def remove_output(self, var: "variable.Variable"): """ - 出力変数を解除する + Remove output variable + + Args: + var(:class:`~webdnn.graph.variable.Variable`): the variable """ # noinspection PyTypeChecker self.remove_next(var) - name = self.get_output_name(var) + name = self._get_output_name(var) self.outputs.pop(name) var.output_from = None - def replace_output(self, v_old: IVariable, v_new: IVariable): + def replace_output(self, v_old: "variable.Variable", v_new: "variable.Variable"): """ - 出力変数を置き換える + Replace output variable with other variable + + Args: + v_old(:class:`~webdnn.graph.variable.Variable`): the variable which is removed + v_new(:class:`~webdnn.graph.variable.Variable`): the variable which is appended """ assert v_old.ndim == v_new.ndim, \ "[operator.replace_output(v_old, v_new)] v_old and v_new must have same number of dimensions." + \ @@ -109,13 +143,13 @@ def replace_output(self, v_old: IVariable, v_new: IVariable): "[operator.replace_output(v_old, v_new)] v_old and v_new must be same shape." + \ f"actual: v_old.shape = {v_old.shape}, v_new.shape = {v_new.shape}" - name = self.get_output_name(v_old) + name = self._get_output_name(v_old) self.remove_output(v_old) self.append_output(name, v_new) def remove_all(self): """ - 全ての変数の接続を解除する + Remove all input and output variables """ for _, v in list(self.inputs.items()): self.remove_input(v) @@ -123,9 +157,13 @@ def remove_all(self): for _, v in list(self.outputs.items()): self.remove_output(v) - def replace(self, new_op: "Operator"): + def replace(self, op_new: "Operator"): """ - 演算を置き換える + Replace this operator with other operator. all variables connected with this operator will be disconnected and + connected to the other operator. + + Args: + op_new(:class:`~webdnn.graph.operator.Operator`): the new operator """ inputs = dict(self.inputs) outputs = dict(self.outputs) @@ -133,10 +171,10 @@ def replace(self, new_op: "Operator"): self.remove_all() for name, var in inputs.items(): - new_op.append_input(name, var) + op_new.append_input(name, var) for name, var in outputs.items(): - new_op.append_output(name, var) + op_new.append_output(name, var) def __repr__(self): return f"""<{self.__class__.__name__} inputs={self.inputs}, outputs={self.outputs}>""" @@ -144,11 +182,11 @@ def __repr__(self): def __str__(self): return self.__repr__() - def __call__(self, *args, **kwargs) -> Tuple[IVariable]: + def __call__(self, *args, **kwargs) -> Tuple["variable.Variable"]: pass def get_attribute(self, Attr: Type[Attribute]) -> List[Attribute]: return [attr for attr in self.attributes if isinstance(attr, Attr)] - def has_attribute(self, Attr: Type[Attribute]) -> List[Attribute]: + def has_attribute(self, Attr: Type[Attribute]) -> bool: return len(self.get_attribute(Attr)) > 0 diff --git a/src/graph_transpiler/webdnn/graph/operators/attributes/axiswise.py b/src/graph_transpiler/webdnn/graph/operators/attributes/axiswise.py index 49fb63417..40dbef89a 100644 --- a/src/graph_transpiler/webdnn/graph/operators/attributes/axiswise.py +++ b/src/graph_transpiler/webdnn/graph/operators/attributes/axiswise.py @@ -7,6 +7,6 @@ class Axiswise(Attribute): axis: Axis - def __init__(self, node: Node, axis: Axis): - super(Axiswise, self).__init__(node) + def __init__(self, base: Node, axis: Axis): + super(Axiswise, self).__init__(base) self.axis = axis diff --git a/src/graph_transpiler/webdnn/graph/operators/attributes/inplace.py b/src/graph_transpiler/webdnn/graph/operators/attributes/inplace.py index 3c1b66030..6e5dc8175 100644 --- a/src/graph_transpiler/webdnn/graph/operators/attributes/inplace.py +++ b/src/graph_transpiler/webdnn/graph/operators/attributes/inplace.py @@ -1,5 +1,4 @@ from webdnn.graph.attribute import Attribute -from webdnn.graph.interface import IOperator from webdnn.graph.node import Node @@ -8,15 +7,15 @@ class Inplace(Attribute): input_name: str output_name: str - def __init__(self, node: Node, input_name: str, output_name: str): - super(Inplace, self).__init__(node) + def __init__(self, base: Node, input_name: str, output_name: str): + super(Inplace, self).__init__(base) self.input_name = input_name self.output_name = output_name def get_input(self): - op = self.node # type: IOperator + op = self.base return op.inputs[self.input_name] def get_output(self): - op = self.node # type: IOperator + op = self.base return op.outputs[self.output_name] diff --git a/src/graph_transpiler/webdnn/graph/operators/attributes/post_axiswise.py b/src/graph_transpiler/webdnn/graph/operators/attributes/post_axiswise.py index 01988af12..daab8afe7 100644 --- a/src/graph_transpiler/webdnn/graph/operators/attributes/post_axiswise.py +++ b/src/graph_transpiler/webdnn/graph/operators/attributes/post_axiswise.py @@ -7,6 +7,6 @@ class PostAxiswise(Attribute): axis: Axis - def __init__(self, node: Node, axis: Axis): - super(PostAxiswise, self).__init__(node) + def __init__(self, base: Node, axis: Axis): + super(PostAxiswise, self).__init__(base) self.axis = axis diff --git a/src/graph_transpiler/webdnn/graph/operators/average_pooling_2d.py b/src/graph_transpiler/webdnn/graph/operators/average_pooling_2d.py index 3da307565..da516aa65 100644 --- a/src/graph_transpiler/webdnn/graph/operators/average_pooling_2d.py +++ b/src/graph_transpiler/webdnn/graph/operators/average_pooling_2d.py @@ -37,10 +37,8 @@ def __call__(self, x: Variable): """ x_shape_dict = x.shape_dict N = x_shape_dict[Axis.N] - H2 = (x_shape_dict[Axis.H] + 2 * self.parameters["padding"][0] - self.parameters["ksize"][0]) // \ - self.parameters["stride"][0] + 1 - W2 = (x_shape_dict[Axis.W] + 2 * self.parameters["padding"][1] - self.parameters["ksize"][1]) // \ - self.parameters["stride"][1] + 1 + H2 = (x_shape_dict[Axis.H] + 2 * self.parameters["padding"][0] - self.parameters["ksize"][0]) // self.parameters["stride"][0] + 1 + W2 = (x_shape_dict[Axis.W] + 2 * self.parameters["padding"][1] - self.parameters["ksize"][1]) // self.parameters["stride"][1] + 1 C2 = x_shape_dict[Axis.C] y = Variable([N, H2, W2, C2], OrderNHWC) diff --git a/src/graph_transpiler/webdnn/graph/operators/axiswise_bias.py b/src/graph_transpiler/webdnn/graph/operators/axiswise_bias.py index 1336165fa..22ffd24a8 100644 --- a/src/graph_transpiler/webdnn/graph/operators/axiswise_bias.py +++ b/src/graph_transpiler/webdnn/graph/operators/axiswise_bias.py @@ -6,6 +6,7 @@ from webdnn.graph.operators.attributes.have_weights import HaveWeights from webdnn.graph.operators.attributes.inplace import Inplace from webdnn.graph.operators.attributes.post_axiswise import PostAxiswise +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable @@ -40,7 +41,8 @@ def __call__(self, x: Variable, b: Variable): tuple of :class:`~webdnn.graph.variable.Variable`: Output """ assert b.ndim == 1 - assert x.shape_dict[self.parameters["axis"]] == b.size + if Placeholder.check_resolved(x.shape_dict[self.parameters["axis"]]) and Placeholder.check_resolved(b.size): + assert x.shape_dict[self.parameters["axis"]] == b.size y = Variable(x.shape, x.order) self.append_input("x", x) self.append_input("b", b) diff --git a/src/graph_transpiler/webdnn/graph/operators/axiswise_scale.py b/src/graph_transpiler/webdnn/graph/operators/axiswise_scale.py index ef45273b8..ac1736539 100644 --- a/src/graph_transpiler/webdnn/graph/operators/axiswise_scale.py +++ b/src/graph_transpiler/webdnn/graph/operators/axiswise_scale.py @@ -6,6 +6,7 @@ from webdnn.graph.operators.attributes.have_weights import HaveWeights from webdnn.graph.operators.attributes.inplace import Inplace from webdnn.graph.operators.attributes.post_axiswise import PostAxiswise +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable @@ -38,7 +39,8 @@ def __call__(self, x: Variable, s: Variable): tuple of :class:`~webdnn.graph.variable.Variable`: Output """ assert s.ndim == 1 - assert x.shape_dict[self.parameters["axis"]] == s.size + if Placeholder.check_resolved(x.shape_dict[self.parameters["axis"]]) and Placeholder.check_resolved(s.size): + assert x.shape_dict[self.parameters["axis"]] == s.size y = Variable(x.shape, x.order) self.append_input("x", x) self.append_input("s", s) diff --git a/src/graph_transpiler/webdnn/graph/operators/clipped_relu.py b/src/graph_transpiler/webdnn/graph/operators/clipped_relu.py new file mode 100644 index 000000000..9936f376a --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/clipped_relu.py @@ -0,0 +1,37 @@ +from typing import Optional + +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.elementwise import Elementwise +from webdnn.graph.operators.attributes.inplace import Inplace +from webdnn.graph.variable import Variable + + +class ClippedRelu(Operator): + """clipped relu activation + + min(max(x, 0), cap) + + Args: + name (str): Operator name. + cap (float): clipping threshold. + + """ + + def __init__(self, name: Optional[str], cap: float): + super().__init__(name) + self.parameters["cap"] = float(cap) + self.attributes = {Elementwise(self), + Inplace(self, "x", "y")} + + def __call__(self, x: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + y = Variable(x.shape, x.order) + self.append_input("x", x) + self.append_output("y", y) + return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/concat.py b/src/graph_transpiler/webdnn/graph/operators/concat.py index d3a2505f3..8d27f7a4e 100644 --- a/src/graph_transpiler/webdnn/graph/operators/concat.py +++ b/src/graph_transpiler/webdnn/graph/operators/concat.py @@ -3,6 +3,7 @@ from webdnn.graph.axis import Axis from webdnn.graph.operator import Operator from webdnn.graph.operators.attributes.elementwise import Elementwise +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable @@ -27,17 +28,18 @@ def __call__(self, *xs: Variable): Returns: tuple of :class:`~webdnn.graph.variable.Variable`: Output """ - concat_axis = self.parameters["axis"] # type: int + concat_axis = self.parameters["axis"] # type: Axis axis_index = xs[0].order.axes_dict[concat_axis] axes_set = set(xs[0].order.axes) - y_shape = list(xs[0].shape) # type: List[int] + y_shape = list(xs[0].shape) # type: List[Placeholder] y_shape[axis_index] = 0 for i, x in enumerate(xs): assert set(x.order.axes) == axes_set for other_axis in [other_axis for other_axis in axes_set if other_axis != concat_axis]: - assert xs[0].shape_dict[other_axis] == x.shape_dict[other_axis] + if Placeholder.check_resolved(xs[0].shape_dict[other_axis]) and Placeholder.check_resolved(x.shape_dict[other_axis]): + assert xs[0].shape_dict[other_axis] == x.shape_dict[other_axis] self.append_input(f"x{i}", x) y_shape[axis_index] += x.shape_dict[concat_axis] diff --git a/src/graph_transpiler/webdnn/graph/operators/convolution2d.py b/src/graph_transpiler/webdnn/graph/operators/convolution2d.py index 581b6d89e..395fce544 100644 --- a/src/graph_transpiler/webdnn/graph/operators/convolution2d.py +++ b/src/graph_transpiler/webdnn/graph/operators/convolution2d.py @@ -5,6 +5,7 @@ from webdnn.graph.operators.attributes.have_weights import HaveWeights from webdnn.graph.operators.util import IntOrTuple, to_tuple from webdnn.graph.order import OrderNHWC +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable @@ -16,14 +17,18 @@ class Convolution2D(Operator): ksize (int or tuple of int): Kernel size. stride (int or tuple of int): Stride size. padding (int or tuple of int): Padding size. + dilation_rate (int or tuple of int): Dilation rate. 1 means ordinary convolution. + Input pixels are shifted by (dilation_rate - 1) pixels. """ - def __init__(self, name: Optional[str], ksize: IntOrTuple, stride: IntOrTuple, padding: IntOrTuple): + def __init__(self, name: Optional[str], ksize: IntOrTuple, stride: IntOrTuple, padding: IntOrTuple, + dilation_rate: Optional[IntOrTuple] = 1): super().__init__(name) self.parameters["ksize"] = to_tuple(ksize) self.parameters["stride"] = to_tuple(stride) self.parameters["padding"] = to_tuple(padding) + self.parameters["dilation_rate"] = to_tuple(dilation_rate) self.attributes = {HaveWeights(self)} def __call__(self, x: Variable, w: Variable) -> Tuple[Variable]: @@ -38,12 +43,14 @@ def __call__(self, x: Variable, w: Variable) -> Tuple[Variable]: x_shape_dict = x.shape_dict w_shape_dict = w.shape_dict - assert (w_shape_dict[Axis.H], w_shape_dict[Axis.W]) == self.ksize - assert w_shape_dict[Axis.C] == x_shape_dict[Axis.C] + if Placeholder.check_resolved(w_shape_dict[Axis.H]) and Placeholder.check_resolved(w_shape_dict[Axis.W]): + assert (w_shape_dict[Axis.H], w_shape_dict[Axis.W]) == self.ksize + if Placeholder.check_resolved(w_shape_dict[Axis.C]) and Placeholder.check_resolved(x_shape_dict[Axis.C]): + assert w_shape_dict[Axis.C] == x_shape_dict[Axis.C] N = x_shape_dict[Axis.N] - H2 = (x_shape_dict[Axis.H] + 2 * self.PH - self.KH) // self.SH + 1 - W2 = (x_shape_dict[Axis.W] + 2 * self.PW - self.KW) // self.SW + 1 + H2 = (x_shape_dict[Axis.H] + 2 * self.PH - self.WH) // self.SH + 1 + W2 = (x_shape_dict[Axis.W] + 2 * self.PW - self.WW) // self.SW + 1 C2 = w_shape_dict[Axis.N] y = Variable([N, H2, W2, C2], OrderNHWC) @@ -65,6 +72,10 @@ def stride(self) -> Tuple[int, int]: def padding(self) -> Tuple[int, int]: return self.parameters["padding"] + @property + def dilation_rate(self) -> Tuple[int, int]: + return self.parameters["dilation_rate"] + @property def KH(self) -> int: return self.ksize[0] @@ -88,3 +99,29 @@ def PH(self) -> int: @property def PW(self) -> int: return self.padding[1] + + @property + def DH(self) -> int: + return self.dilation_rate[0] + + @property + def DW(self) -> int: + return self.dilation_rate[1] + + @property + def WH(self) -> int: + """ + Input window height considering dilation. + Returns: + + """ + return self.DH * (self.KH - 1) + 1 + + @property + def WW(self) -> int: + """ + Input window width considering dilation. + Returns: + + """ + return self.DW * (self.KW - 1) + 1 diff --git a/src/graph_transpiler/webdnn/graph/operators/deconvolution2d.py b/src/graph_transpiler/webdnn/graph/operators/deconvolution2d.py index 08871351b..3f9867d81 100644 --- a/src/graph_transpiler/webdnn/graph/operators/deconvolution2d.py +++ b/src/graph_transpiler/webdnn/graph/operators/deconvolution2d.py @@ -1,10 +1,11 @@ -from typing import Tuple +from typing import Tuple, Optional from webdnn.graph.axis import Axis from webdnn.graph.operator import Operator from webdnn.graph.operators.attributes.have_weights import HaveWeights from webdnn.graph.operators.util import IntOrTuple, to_tuple from webdnn.graph.order import OrderNHWC +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable @@ -19,7 +20,7 @@ class Deconvolution2D(Operator): """ - def __init__(self, name: str, ksize: IntOrTuple, stride: IntOrTuple, padding: IntOrTuple): + def __init__(self, name: Optional[str], ksize: IntOrTuple, stride: IntOrTuple, padding: IntOrTuple): super().__init__(name) self.parameters["ksize"] = to_tuple(ksize) self.parameters["stride"] = to_tuple(stride) @@ -37,8 +38,10 @@ def __call__(self, x: Variable, w: Variable): """ x_shape_dict = x.shape_dict w_shape_dict = w.shape_dict - assert (w_shape_dict[Axis.H], w_shape_dict[Axis.W]) == self.ksize - assert w_shape_dict[Axis.C] == x_shape_dict[Axis.C] + if Placeholder.check_resolved(w_shape_dict[Axis.H]) and Placeholder.check_resolved(w_shape_dict[Axis.W]): + assert (w_shape_dict[Axis.H], w_shape_dict[Axis.W]) == self.ksize + if Placeholder.check_resolved(w_shape_dict[Axis.C]) and Placeholder.check_resolved(x_shape_dict[Axis.C]): + assert w_shape_dict[Axis.C] == x_shape_dict[Axis.C] N = x_shape_dict[Axis.N] H2 = (x_shape_dict[Axis.H] - 1) * self.SH - 2 * self.PH + self.KH diff --git a/src/graph_transpiler/webdnn/graph/operators/elementwise_sum.py b/src/graph_transpiler/webdnn/graph/operators/elementwise_sum.py index ae9535121..a332aa504 100644 --- a/src/graph_transpiler/webdnn/graph/operators/elementwise_sum.py +++ b/src/graph_transpiler/webdnn/graph/operators/elementwise_sum.py @@ -3,6 +3,7 @@ from webdnn.graph.operator import Operator from webdnn.graph.operators.attributes.elementwise import Elementwise from webdnn.graph.operators.attributes.inplace import Inplace +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable @@ -29,7 +30,10 @@ def __call__(self, *xs: Variable): y = Variable(xs[0].shape, xs[0].order) for i, x in enumerate(xs): for axis in x.order.axes: - assert axis in y.order.axes and y.shape_dict[axis] == x.shape_dict[axis] + assert axis in y.order.axes + + if Placeholder.check_resolved(x.shape_dict[axis]) or Placeholder.check_resolved(y.shape_dict[axis]): + assert y.shape_dict[axis] == x.shape_dict[axis] self.append_input(f"x{i}", x) self.append_output("y", y) diff --git a/src/graph_transpiler/webdnn/graph/operators/embedding.py b/src/graph_transpiler/webdnn/graph/operators/embedding.py new file mode 100644 index 000000000..289741f82 --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/embedding.py @@ -0,0 +1,49 @@ +from typing import Optional + +from webdnn.graph.axis import Axis +from webdnn.graph.order import OrderNTC, OrderNT, OrderNC, OrderCN +from webdnn.graph.operator import Operator +from webdnn.graph.variable import Variable + + +class Embedding(Operator): + """Word embedding layer. + + Args: + name (str): Operator name. + + """ + + def __init__(self, name: Optional[str]): + super().__init__(name) + self.attributes = set() + + def __call__(self, x: Variable, w: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input (sequence) + w (:class:`~webdnn.graph.variable.Variable`): Weight + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + self.append_input("x", x) + self.append_input("w", w) + + # @TODO: this is too strict condition. It should be supported in optimization phase, not here. + if x.order != OrderNT: + raise NotImplementedError("Currently, Embedding supports only OrderNT variable for input sequence variable.") + + x_shape_dict = x.shape_dict + w_shape_dict = w.shape_dict + + assert set(w.order.axes) == {Axis.N, Axis.C} + + batch_size = x_shape_dict[Axis.N] + sequence_len = x_shape_dict[Axis.T] + embedding_dim = w_shape_dict[Axis.N] + + y = Variable([batch_size, sequence_len, embedding_dim], OrderNTC) + + self.append_output("y", y) + return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/flatten.py b/src/graph_transpiler/webdnn/graph/operators/flatten.py index 80071c111..58bee7885 100644 --- a/src/graph_transpiler/webdnn/graph/operators/flatten.py +++ b/src/graph_transpiler/webdnn/graph/operators/flatten.py @@ -11,6 +11,9 @@ # FIXME: improve documentation +from webdnn.util.misc import mul + + class Flatten(Operator): """Flatten some axes into one axis. @@ -59,7 +62,7 @@ def __call__(self, x: Variable): raise ValueError(f"Axis {axis} is duplicated") out_axes.append(self.parameters["out_axis"]) - out_shape.append(int(np.prod([x.shape_dict[axis] for axis in self.parameters["in_axes"]]))) + out_shape.append(mul([x.shape_dict[axis] for axis in self.parameters["in_axes"]])) y = Variable(out_shape, Order(out_axes)) self.append_input("x", x) diff --git a/src/graph_transpiler/webdnn/graph/operators/hard_sigmoid.py b/src/graph_transpiler/webdnn/graph/operators/hard_sigmoid.py new file mode 100644 index 000000000..5438994bd --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/hard_sigmoid.py @@ -0,0 +1,35 @@ +from typing import Optional + +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.elementwise import Elementwise +from webdnn.graph.operators.attributes.inplace import Inplace +from webdnn.graph.variable import Variable + + +class HardSigmoid(Operator): + """Hard Sigmoid activation + + clip(0.2 * x + 0.5, 0.0, 1.0) + + Args: + name (str): Operator name. + + """ + + def __init__(self, name: Optional[str]): + super().__init__(name) + self.attributes = {Elementwise(self), + Inplace(self, "x", "y")} + + def __call__(self, x: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + y = Variable(x.shape, x.order) + self.append_input("x", x) + self.append_output("y", y) + return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/leaky_relu.py b/src/graph_transpiler/webdnn/graph/operators/leaky_relu.py new file mode 100644 index 000000000..80de628f4 --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/leaky_relu.py @@ -0,0 +1,37 @@ +from typing import Optional + +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.elementwise import Elementwise +from webdnn.graph.operators.attributes.inplace import Inplace +from webdnn.graph.variable import Variable + + +class LeakyRelu(Operator): + """leaky relu activation + + max(x, slope*x) + + Args: + name (str): Operator name. + slope (float): slope in negative input. + + """ + + def __init__(self, name: Optional[str], slope: float): + super().__init__(name) + self.parameters["slope"] = float(slope) + self.attributes = {Elementwise(self), + Inplace(self, "x", "y")} + + def __call__(self, x: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + y = Variable(x.shape, x.order) + self.append_input("x", x) + self.append_output("y", y) + return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/linear.py b/src/graph_transpiler/webdnn/graph/operators/linear.py index 68a8c8ee3..8d4bb8d8b 100644 --- a/src/graph_transpiler/webdnn/graph/operators/linear.py +++ b/src/graph_transpiler/webdnn/graph/operators/linear.py @@ -5,6 +5,7 @@ from webdnn.graph.operators.attributes.have_weights import HaveWeights from webdnn.graph.operators.attributes.post_axiswise import PostAxiswise from webdnn.graph.order import OrderNC +from webdnn.graph.placeholder import Placeholder from webdnn.graph.variable import Variable @@ -39,11 +40,14 @@ def __call__(self, x: Variable, w: Variable): x_shape_dict = x.shape_dict w_shape_dict = w.shape_dict - assert x_shape_dict[Axis.C] == w_shape_dict[Axis.C] + if Placeholder.check_resolved(x_shape_dict[Axis.C]) and Placeholder.check_resolved(w_shape_dict[Axis.C]): + assert x_shape_dict[Axis.C] == w_shape_dict[Axis.C] assert len(x_shape_dict) == len(w_shape_dict) if len(x_shape_dict) == 4: - assert x_shape_dict[Axis.H] == w_shape_dict[Axis.H] - assert x_shape_dict[Axis.W] == w_shape_dict[Axis.W] + if Placeholder.check_resolved(x_shape_dict[Axis.H]) and Placeholder.check_resolved(w_shape_dict[Axis.H]): + assert x_shape_dict[Axis.H] == w_shape_dict[Axis.H] + if Placeholder.check_resolved(x_shape_dict[Axis.W]) and Placeholder.check_resolved(w_shape_dict[Axis.W]): + assert x_shape_dict[Axis.W] == w_shape_dict[Axis.W] y = Variable([x_shape_dict[Axis.N], w_shape_dict[Axis.N]], OrderNC) self.append_output("y", y) return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/local_response_normalization.py b/src/graph_transpiler/webdnn/graph/operators/local_response_normalization.py index 130a05171..3728343f6 100644 --- a/src/graph_transpiler/webdnn/graph/operators/local_response_normalization.py +++ b/src/graph_transpiler/webdnn/graph/operators/local_response_normalization.py @@ -10,7 +10,8 @@ # FIXME: Improve documentation class LocalResponseNormalization(Operator): - """Operator same as local response normalization layer in Caffe. + """Operator same as local response normalization layer in Caffe. + Only cross channel mode is supported; normalization is done for channel axis. see: http://caffe.berkeleyvision.org/tutorial/layers/lrn.html @@ -30,8 +31,7 @@ def __init__(self, name: Optional[str], n: float, k: float, alpha: float, beta: self.parameters["alpha"] = alpha self.parameters["beta"] = beta self.attributes = {PostAxiswise(self, Axis.C), - Axiswise(self, Axis.C), - Inplace(self, "x", "y")} + Axiswise(self, Axis.C)} def __call__(self, x: Variable): """ diff --git a/src/graph_transpiler/webdnn/graph/operators/lstm.py b/src/graph_transpiler/webdnn/graph/operators/lstm.py new file mode 100644 index 000000000..555d36104 --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/lstm.py @@ -0,0 +1,109 @@ +from typing import Optional + +from webdnn.graph.axis import Axis +from webdnn.graph.operator import Operator +from webdnn.graph.order import OrderNTC, OrderNC, OrderC +from webdnn.graph.variable import Variable + + +class LSTM(Operator): + """Long-short term memory layer. + + Currently, outputs value of hidden layer after final input is consumed. + Details are corresponding to Keras's implementation (layers/recurrent.py) + + Args: + name (str): Operator name. + + """ + + def __init__(self, name: Optional[str], use_bias: bool, return_sequences: bool, + use_initial_c: bool, use_initial_h: bool, + activation: str, recurrent_activation: str): + # TODO: accept selection of activation function + super().__init__(name) + self.parameters["use_bias"] = use_bias + self.parameters["return_sequences"] = return_sequences + self.parameters["use_initial_c"] = use_initial_c + self.parameters["use_initial_h"] = use_initial_h + assert activation in ["tanh"], "unknown activation function" + self.parameters["activation"] = activation + assert recurrent_activation in ["hard_sigmoid", "sigmoid"], "unknown recurrent activation function" + self.parameters["recurrent_activation"] = recurrent_activation + self.attributes = set() + + def __call__(self, x: Variable, w_input: Variable, w_hidden: Variable, b: Optional[Variable] = None, + initial_c: Optional[Variable] = None, initial_h: Optional[Variable] = None): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input (sequence OrderNTC) + w_input (:class:`~webdnn.graph.variable.Variable`): Weight for input + w_hidden (:class:`~webdnn.graph.variable.Variable`): Weight for hidden state + b (:class:`~webdnn.graph.variable.Variable`): Bias + initial_c (:class:`~webdnn.graph.variable.Variable`): Initial cell state + initial_h (:class:`~webdnn.graph.variable.Variable`): Initial hidden state + + Returns: + y (:class:`~webdnn.graph.variable.Variable`): Output (OrderNC) + final_c (:class:`~webdnn.graph.variable.Variable`): Last cell state (OrderNC) + """ + assert self.parameters["use_bias"] == (b is not None) + assert self.parameters["use_initial_c"] == (initial_c is not None) + assert self.parameters["use_initial_h"] == (initial_h is not None) + + self.append_input("x", x) + self.append_input("w_input", w_input) + self.append_input("w_hidden", w_hidden) + + if b is not None: + self.append_input("b", b) + + # @TODO: this is too strict condition. It should be supported in optimization phase, not here. + if x.order != OrderNTC: + raise NotImplementedError("Currently, LSTM supports only OrderNTC variable for input sequence variable.") + + x_shape_dict = x.shape_dict + w_input_shape_dict = w_input.shape_dict + w_hidden_shape_dict = w_hidden.shape_dict + + assert set(x.order.axes) == {Axis.N, Axis.T, Axis.C} + assert set(w_input.order.axes) == {Axis.N, Axis.C} + assert set(w_hidden.order.axes) == {Axis.N, Axis.C} + assert b.order == OrderC + + batch_size = x_shape_dict[Axis.N] + sequence_len = x_shape_dict[Axis.T] + input_dim = x_shape_dict[Axis.C] + hidden_dim = w_hidden_shape_dict[Axis.C] + + assert x_shape_dict[Axis.N] == batch_size + assert x_shape_dict[Axis.C] == w_input_shape_dict[Axis.C] == input_dim + assert w_input_shape_dict[Axis.N] == w_hidden_shape_dict[Axis.N] == hidden_dim * 4 + + if initial_c is not None: + self.append_input("initial_c", initial_c) + initial_c_shape_dict = initial_c.shape_dict + + assert set(initial_c.order.axes) == {Axis.N, Axis.C} + assert initial_c_shape_dict[Axis.N] == batch_size + assert initial_c_shape_dict[Axis.C] == hidden_dim + + if initial_h is not None: + self.append_input("initial_h", initial_h) + initial_h_shape_dict = initial_h.shape_dict + + assert set(initial_h.order.axes) == {Axis.N, Axis.C} + assert initial_h_shape_dict[Axis.N] == batch_size + assert initial_h_shape_dict[Axis.C] == hidden_dim + + if self.parameters["return_sequences"]: + y = Variable([batch_size, sequence_len, hidden_dim], OrderNTC) + else: + y = Variable([batch_size, hidden_dim], OrderNC) + + final_c = Variable([batch_size, hidden_dim], OrderNC) + + self.append_output("y", y) + self.append_output("final_c", final_c) + + return y, final_c diff --git a/src/graph_transpiler/webdnn/graph/operators/reinterpret_axis.py b/src/graph_transpiler/webdnn/graph/operators/reinterpret_axis.py new file mode 100644 index 000000000..6e025dbfd --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/reinterpret_axis.py @@ -0,0 +1,48 @@ +from typing import Optional, List, Union + +import numpy as np + +from webdnn.graph.axis import Axis +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.elementwise import Elementwise +from webdnn.graph.operators.attributes.inplace import Inplace +from webdnn.graph.order import Order +from webdnn.graph.placeholder import Placeholder +from webdnn.graph.variable import Variable + + +from webdnn.util.misc import mul + + +class ReinterpretAxis(Operator): + """Re-interpret an axis as another semantics. Shape is not changed. + + Args: + name (str): Operator name. + + """ + + def __init__(self, name: Optional[str], in_order: Order, out_order: Order): + + super().__init__(name) + + self.parameters["in_order"] = in_order + self.parameters["out_order"] = out_order + assert in_order.ndim == out_order.ndim + self.attributes = {} + + def __call__(self, x: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + assert self.parameters["in_order"] == x.order + + y = Variable(x.shape, self.parameters["out_order"]) + self.append_input("x", x) + self.append_output("y", y) + + return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/reshape.py b/src/graph_transpiler/webdnn/graph/operators/reshape.py index 239688dbe..35248b50b 100644 --- a/src/graph_transpiler/webdnn/graph/operators/reshape.py +++ b/src/graph_transpiler/webdnn/graph/operators/reshape.py @@ -1,43 +1,60 @@ -# from typing import Dict, List -# -# from webdnn.graph.operator import Operator -# from webdnn.graph.operators.attributes.inplace import Inplace -# from webdnn.graph.operators.attributes.post_axiswise import PostAxiswise -# from webdnn.graph.operators.attributes.post_elementwise import PostElementwise -# from webdnn.graph.variable import Variable -# -# -# class Reshape(Operator): -# """ -# 入力変数の形を変形するレイヤー -# 形状変化を表現する便宜上のもので、データ操作はない -# """ -# attributes = {PostElementwise, -# PostAxiswise, -# Inplace} -# -# def __init__(self, name: str, parameters: Dict[str, object]): -# """ -# parameters: {out_shape: Tuple, out_order: Order} -# :param name: -# :param parameters: -# """ -# raise NotImplementedError() # 入力オーダーの定義がなく、中途半端なので使い方を決めてから再実装 -# assert "out_shape" in parameters -# assert "out_order" in parameters -# assert issubclass(type(parameters["out_order"]), AxisOrder) -# super().__init__(name, parameters) -# -# def __call__(self, x: Variable): -# """ -# Args: -# x (:class:`~webdnn.graph.variable.Variable`): Input -# -# Returns: -# tuple of :class:`~webdnn.graph.variable.Variable`: Output -# """ -# out_shape = self.parameters["out_shape"] # type: List[int] -# y = Variable(out_shape, self.parameters["out_order"]) -# self.append_input("x", x) -# self.append_output("y", y) -# return y, +from typing import Optional, List, Union + +import numpy as np + +from webdnn.graph.axis import Axis +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.elementwise import Elementwise +from webdnn.graph.operators.attributes.inplace import Inplace +from webdnn.graph.order import Order +from webdnn.graph.placeholder import Placeholder +from webdnn.graph.variable import Variable + + +from webdnn.util.misc import mul + + +class Reshape(Operator): + """Reshape array assuming it is C-order. + Removing / inserting axis with length 1 + + When in_order: NHWC, out_order: NTC, out_shape: (2, 6, 10) and input variable is (2, 3, 4, 5), the semantic procedure is as follows. + 1. Interpret input variable as NTHWC (2, 1, 3, 4, 5) with inserting axis with length 1 + 2. Reshape it with assuming C-order and length of axis being removed is 1; NTHWC (2, 6, 1, 1, 10) + 3. Remove axes; NTC (2, 6, 10) + + Swapping axes is prohibited because it is ambiguous. + If in_order and out_order match the actual input / output variable order, kernel does not have to do anything. + + Args: + name (str): Operator name. + + """ + + def __init__(self, name: Optional[str], in_order: Order, out_order: Order, out_shape: List[Union[int, Placeholder]]): + + super().__init__(name) + + self.parameters["in_order"] = in_order + self.parameters["out_order"] = out_order + assert -1 not in out_shape, "-1 (wildcard) in reshape output shape is currently not supported" + self.parameters["out_shape"] = out_shape + + self.attributes = {} + + def __call__(self, x: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + assert self.parameters["in_order"] == x.order + + y = Variable(self.parameters["out_shape"], self.parameters["out_order"]) + assert y.shape == self.parameters["out_shape"] + self.append_input("x", x) + self.append_output("y", y) + + return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/softmax.py b/src/graph_transpiler/webdnn/graph/operators/softmax.py index bc6e5e8ba..3ab24b408 100644 --- a/src/graph_transpiler/webdnn/graph/operators/softmax.py +++ b/src/graph_transpiler/webdnn/graph/operators/softmax.py @@ -1,19 +1,21 @@ from typing import Optional +from webdnn.graph.axis import Axis from webdnn.graph.operator import Operator from webdnn.graph.operators.attributes.inplace import Inplace from webdnn.graph.variable import Variable class Softmax(Operator): - """Softmax operator + """Softmax operator Args: name (str): Operator name. """ - def __init__(self, name: Optional[str]): + def __init__(self, name: Optional[str], axis: Axis): super().__init__(name) + self.parameters["axis"] = axis self.attributes = {Inplace(self, "x", "y")} def __call__(self, x: Variable): diff --git a/src/graph_transpiler/webdnn/graph/operators/softplus.py b/src/graph_transpiler/webdnn/graph/operators/softplus.py new file mode 100644 index 000000000..50bc24765 --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/softplus.py @@ -0,0 +1,38 @@ +from typing import Optional + +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.elementwise import Elementwise +from webdnn.graph.operators.attributes.inplace import Inplace +from webdnn.graph.variable import Variable + + +class Softplus(Operator): + """Softplus activation + + log(1 + exp(beta * x)) / beta + + Args: + name (str): Operator name. + beta (float): coefficient. + + """ + + def __init__(self, name: Optional[str], beta: float): + super().__init__(name) + # in most case, beta == 1.0. Separating case of beta == 1.0 may improve performance. + self.parameters["beta"] = float(beta) + self.attributes = {Elementwise(self), + Inplace(self, "x", "y")} + + def __call__(self, x: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + y = Variable(x.shape, x.order) + self.append_input("x", x) + self.append_output("y", y) + return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/softsign.py b/src/graph_transpiler/webdnn/graph/operators/softsign.py new file mode 100644 index 000000000..f1b21dc3c --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/softsign.py @@ -0,0 +1,36 @@ +from typing import Optional + +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.elementwise import Elementwise +from webdnn.graph.operators.attributes.inplace import Inplace +from webdnn.graph.variable import Variable + + +class Softsign(Operator): + """Softsign activation + + https://www.tensorflow.org/api_docs/python/tf/nn/softsign + x / (abs(x) + 1) + + Args: + name (str): Operator name. + + """ + + def __init__(self, name: Optional[str]): + super().__init__(name) + self.attributes = {Elementwise(self), + Inplace(self, "x", "y")} + + def __call__(self, x: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + y = Variable(x.shape, x.order) + self.append_input("x", x) + self.append_output("y", y) + return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/zero_padding_1d.py b/src/graph_transpiler/webdnn/graph/operators/zero_padding_1d.py new file mode 100644 index 000000000..4b94a313d --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/zero_padding_1d.py @@ -0,0 +1,45 @@ +from typing import Optional + +from webdnn.graph.axis import Axis +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.axiswise import Axiswise +from webdnn.graph.operators.attributes.post_axiswise import PostAxiswise +from webdnn.graph.operators.util import IntOrTuple, to_tuple +from webdnn.graph.order import OrderNTC +from webdnn.graph.variable import Variable + + +class ZeroPadding1D(Operator): + """Zero padding 1D operator + + Add padding to time-series data (n, t, c) -> (n, left + t + right, c) + + Args: + name (str): Operator name. + padding (int or tuple of int): Padding size. [left, right] + """ + + def __init__(self, name: Optional[str], padding: IntOrTuple): + super().__init__(name) + self.parameters["padding"] = to_tuple(padding) + self.attributes = {PostAxiswise(self, Axis.C), + Axiswise(self, Axis.C)} + + def __call__(self, x: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + x_shape_dict = x.shape_dict + N = x_shape_dict[Axis.N] + T2 = x_shape_dict[Axis.T] + self.parameters["padding"][0] + self.parameters["padding"][1] + C2 = x_shape_dict[Axis.C] + + y = Variable([N, T2, C2], OrderNTC) + + self.append_input("x", x) + self.append_output("y", y) + return y, diff --git a/src/graph_transpiler/webdnn/graph/operators/zero_padding_2d.py b/src/graph_transpiler/webdnn/graph/operators/zero_padding_2d.py new file mode 100644 index 000000000..4ad315aa6 --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/operators/zero_padding_2d.py @@ -0,0 +1,46 @@ +from typing import Optional + +from webdnn.graph.axis import Axis +from webdnn.graph.operator import Operator +from webdnn.graph.operators.attributes.axiswise import Axiswise +from webdnn.graph.operators.attributes.post_axiswise import PostAxiswise +from webdnn.graph.operators.util import IntOrTuple, to_tuple +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +class ZeroPadding2D(Operator): + """Zero padding 2D operator + + Supposed to be merged into convolution in optimization + + Args: + name (str): Operator name. + padding (int or tuple of int): Padding size. [top, left] + """ + + def __init__(self, name: Optional[str], padding: IntOrTuple): + super().__init__(name) + self.parameters["padding"] = to_tuple(padding) + self.attributes = {PostAxiswise(self, Axis.C), + Axiswise(self, Axis.C)} + + def __call__(self, x: Variable): + """ + Args: + x (:class:`~webdnn.graph.variable.Variable`): Input + + Returns: + tuple of :class:`~webdnn.graph.variable.Variable`: Output + """ + x_shape_dict = x.shape_dict + N = x_shape_dict[Axis.N] + H2 = x_shape_dict[Axis.H] + 2 * self.parameters["padding"][0] + W2 = x_shape_dict[Axis.W] + 2 * self.parameters["padding"][1] + C2 = x_shape_dict[Axis.C] + + y = Variable([N, H2, W2, C2], OrderNHWC) + + self.append_input("x", x) + self.append_output("y", y) + return y, diff --git a/src/graph_transpiler/webdnn/graph/optimize_rule.py b/src/graph_transpiler/webdnn/graph/optimize_rule.py index ecb9551d5..e26c0bebb 100644 --- a/src/graph_transpiler/webdnn/graph/optimize_rule.py +++ b/src/graph_transpiler/webdnn/graph/optimize_rule.py @@ -5,16 +5,17 @@ # FIXME: DOCS class OptimizeRule: - sub_rules: List["OptimizeRule"] - def __init__(self): - self.sub_rules = [] + self.sub_rules = [] # type:List["OptimizeRule"] def optimize(self, graph: Graph) -> Tuple[Graph, bool]: - """ - graphを変換する - :param graph: - :return: 2要素のタプル。第一要素は変換後のGraph, 第二要素は変換があったかどうかのbool値 + """Optimize the computational graph + + params: + graph: Computational graph + + return: + optimized graph and flag whether the graph is changed or not. """ flag_retry = True flag_totally_changed = False diff --git a/src/graph_transpiler/webdnn/graph/order.py b/src/graph_transpiler/webdnn/graph/order.py index ab1d13460..59e2f048c 100644 --- a/src/graph_transpiler/webdnn/graph/order.py +++ b/src/graph_transpiler/webdnn/graph/order.py @@ -2,22 +2,23 @@ from webdnn.graph.axis import Axis -""" -This attribute means data order, not number of dimensions -""" - -# FIXME: DOCS -# FIXME: Is it need to extend from Attribute? class Order: - ndim: int - axes: List[Axis] - axes_dict: Dict[Axis, int] + """ + This class represents semantics of data order of variables. + + For example, :code:`OrderNHWC` means that the data is aligned as Channel-major(Batch-size-minor). + + attrs: + ndim(int): number of dimensions + axes(list of :class:`~webdnn.graph.axis.Axis`): list of axis + axes_dict(dict of :class:`~webdnn.graph.axis.Axis` and int): dictionary of pairs of axis and order index + """ def __init__(self, axes: List[Axis]): - self.ndim = len(axes) - self.axes = axes - self.axes_dict = {a: i for i, a in enumerate(axes)} + self.ndim = len(axes) # type: int + self.axes = axes # type: List[Axis] + self.axes_dict = {a: i for i, a in enumerate(axes)} # type: Dict[Axis, int] def __eq__(self, other): if isinstance(other, Order): @@ -86,3 +87,6 @@ def __str__(self): Chainer Deconvolution2D Filter """ OrderCHWN = Order([Axis.C, Axis.H, Axis.W, Axis.N]) + +OrderNT = Order([Axis.N, Axis.T]) +OrderNTC = Order([Axis.N, Axis.T, Axis.C]) diff --git a/src/graph_transpiler/webdnn/graph/placeholder.py b/src/graph_transpiler/webdnn/graph/placeholder.py new file mode 100644 index 000000000..a92be750d --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/placeholder.py @@ -0,0 +1,655 @@ +from enum import auto, Enum +from typing import Union, Optional, List + +import numpy as np + +from webdnn.util.json import json + + +class PlaceholderOperator(Enum): + Add = auto() # v1 + v2 + Sub = auto() # v1 - v2 + Mul = auto() # v1 * v2 + Mod = auto() # v1 % v2 + FloorDiv = auto() # v1 // v2 + + +class Dependency: + operator: PlaceholderOperator + operands: List[Union[int, "Placeholder"]] + + @staticmethod + def check_deep_equal(d1: "Dependency", d2: "Dependency") -> bool: + if d1.operator != d2.operator: + return False + + if d1.operator == PlaceholderOperator.Mul or d1.operator == PlaceholderOperator.Add: + operands1 = sorted(d1.operands, key=lambda op: str(op)) + operands2 = sorted(d2.operands, key=lambda op: str(op)) + if len(operands1) != len(operands2): + return False + + return all([Placeholder._check_deep_equal(p1, p2) for p1, p2 in zip(operands1, operands2)]) + + elif d1.operator == PlaceholderOperator.Mod or \ + d1.operator == PlaceholderOperator.Mod or \ + d1.operator == PlaceholderOperator.FloorDiv: + return Placeholder._check_deep_equal(d1.operands[0], d2.operands[0]) and \ + Placeholder._check_deep_equal(d1.operands[1], d2.operands[1]) + + def __init__(self, operator: PlaceholderOperator, operands: List[Union[int, "Placeholder"]]): + self.operator = operator + operands = list(operands) + + if operator == PlaceholderOperator.Add: + i = 0 + while i < len(operands): + v1 = operands[i] + if v1 == 0: + operands.pop(i) + continue + + j = i + 1 + while j < len(operands): + v2 = operands[j] + if v2 == 0: + operands.pop(j) + continue + + # v = ... + v1 + v2 + ... + # v1 = common_term * other_term1 + # v2 = common_term * other_term2 + # + # + # >> When other_term1 and other_term2 are resolved, + # + # v = (... + (other_term1 + other_term2) * common_term + ...) + other_term1 = list(v1.dependency.operands) if v1.dependency else {v1} + other_term2 = list(v2.dependency.operands) if v2.dependency else {v2} + common_term = list() + + for vv in list(other_term1): + if vv in other_term2: + other_term1.remove(vv) + other_term2.remove(vv) + common_term.append(vv) + + for vv in list(other_term2): + if vv in other_term1: + other_term1.remove(vv) + other_term2.remove(vv) + common_term.append(vv) + + if all(p.is_resolved for p in other_term1) and all(p.is_resolved for p in other_term2): + other_term1 = Placeholder(Dependency(PlaceholderOperator.Mul, list(other_term1))) + other_term2 = Placeholder(Dependency(PlaceholderOperator.Mul, list(other_term2))) + common_term = Placeholder(Dependency(PlaceholderOperator.Mul, list(common_term))) + operands.pop(j) + operands.pop(i) + j -= 1 + i -= 1 + operands.append(Placeholder(value=(other_term1 + other_term2) * common_term)) + + j += 1 + i += 1 + + elif operator == PlaceholderOperator.Mul: + s = 1 + i = 0 + while i < len(operands): + v1 = operands[i] + if v1.is_resolved: + s *= v1 + operands.pop(i) + i -= 1 + i += 1 + + if s != 1: + operands.append(Placeholder(value=s)) + + self.operands = operands + + @property + def is_resolved(self) -> bool: + """ + If true, all dependent placeholders are resolved + """ + for operand in self.operands: + if isinstance(operand, Placeholder) and not operand.is_resolved: + return False + + return True + + @property + def value(self) -> Union[int, "Placeholder"]: + if not self.is_resolved: + return Placeholder(self) + + if self.operator == PlaceholderOperator.Add: + r = 0 + for v in self.operands: + r += v.value + return r + + elif self.operator == PlaceholderOperator.Sub: + raise NotImplementedError + + elif self.operator == PlaceholderOperator.Mul: + r = 1 + for v in self.operands: + r *= v.value + return r + + elif self.operator == PlaceholderOperator.Mod: + return self.operands[0].value % self.operands[1].value + + elif self.operator == PlaceholderOperator.FloorDiv: + return self.operands[0].value // self.operands[1].value + + else: + raise NotImplementedError(f"Unsupported placeholder operation: {self.operator}") + + def __repr__(self): + if self.operator == PlaceholderOperator.Add: + return " + ".join(map(lambda x: x.__repr__(), self.operands)) + + elif self.operator == PlaceholderOperator.Sub: + return " - ".join(map(lambda x: x.__repr__(), self.operands)) + + s = [] + for v in self.operands: + if v.dependency and len(v.dependency.operands) > 1: + s.append("(" + v.__repr__() + ")") + else: + s.append(v.__repr__()) + + if self.operator == PlaceholderOperator.Mul: + return " * ".join(s) + + elif self.operator == PlaceholderOperator.Mod: + return " % ".join(s) + + elif self.operator == PlaceholderOperator.FloorDiv: + return " // ".join(s) + + raise NotImplementedError(f"Unsupported placeholder operation: {self.operator}") + + def dump(self): + if self.operator == PlaceholderOperator.Add: + return "(" + self.operands[0].dump() + " + " + self.operands[1].dump() + ")" + + elif self.operator == PlaceholderOperator.Sub: + return "(" + self.operands[0].dump() + " - " + self.operands[1].dump() + ")" + + elif self.operator == PlaceholderOperator.Mul: + return self.operands[0].dump() + " * " + self.operands[1].dump() + + elif self.operator == PlaceholderOperator.Mod: + return self.operands[0].dump() + " % " + self.operands[1].dump() + + elif self.operator == PlaceholderOperator.FloorDiv: + return self.operands[0].dump() + " // " + self.operands[1].dump() + + else: + raise NotImplementedError(f"Unsupported placeholder operation: {self.operator}") + + def generate_js_function(self): + if self.operator == PlaceholderOperator.Add: + return " + ".join(map(lambda x: x.generate_js_function(flag_semicolon=False), self.operands)) + + elif self.operator == PlaceholderOperator.Sub: + return " - ".join(map(lambda x: x.generate_js_function(flag_semicolon=False), self.operands)) + + s = [] + for v in self.operands: + if v.dependency and len(v.dependency.operands) > 1: + s.append("(" + v.generate_js_function(flag_semicolon=False) + ")") + else: + s.append(v.generate_js_function(flag_semicolon=False)) + + if self.operator == PlaceholderOperator.Mul: + return " * ".join(s) + + elif self.operator == PlaceholderOperator.Mod: + return " % ".join(s) + + elif self.operator == PlaceholderOperator.FloorDiv: + assert len(s) == 2 + return f"Math.floor({s[0]} / {s[1]})" + + raise NotImplementedError(f"Unsupported placeholder operation: {self.operator}") + + +_id = 0 + + +class Placeholder(json.SerializableMixin): + """ + Placeholder represents values which are unknown at compile time and determined at runtime, like batch size, length of + time series, etc. + + Placeholder supports only integer value. + + Placeholder is embedded in graph descriptor and resolved at runtime like follows: + + .. code-block:: javascript + + runner = await WebDNN.load('./my_model'); + runner.setPlaceholderValue({ + 'N': 8 + }); + + Also Placeholder can be resolved by setting concrete value at compile time. + + .. code-block:: python + + >> x = Placeholder(label="x") + >> print(x) + + >> x.value = 3 + >> print(x) + 3 + >> print(type(x)) + + + Placeholder supports follow basic operations. + + - add(:code:`+`), sub(:code:`-`), mul(:code:`*`), mod(:code:`%`), floor-div(:code:`//`). + + .. code-block:: python + + >> x = Placeholder(label="x") + >> y = x * 2 + 3 + >> print(y) + * 2 + 3 + + .. code-block:: python + + >> x = Placeholder(label="x") + >> y = x % 4 // 5 + >> print(y) + ( % 4) // 5 + + If possible, equation are simplified + + .. code-block:: python + + >> x = Placeholder(label="x") + >> y = x * 6 + x * 7 + >> print(y) + * 13 + + - eq(:code:`==`), ne(:code:`!=`) + + If both placeholders are resolved, they are compared based on concrete values. + + .. code-block:: python + + >> p = Placeholder(value=3) + >> x = p * 2 + >> y = p + p + >> print(x == y == 6) + True + + If either placeholder is not resolved, they are compared symbolically. + + .. code-block:: python + + >> p = Placeholder(label="p") + >> x = p * 2 + >> y = p * 3 + >> print(x == y) + False + >> p.value = 0 + >> print(x == y) + True + + + - gt(:code:`>`), lt(:code:`<`), ge(:code:`>=`), le(:code:`<=`) + + Supported only when both placeholders are resolved. Otherwise, an error is raised. + + .. code-block:: python + + >> p = Placeholder(label="p") + >> x = p * 2 + >> y = p * 3 + >> print(x < y) + ValueError: First operand is unresolved placeholder. It can't be compared. + >> p.value = 3 + >> print(x < y) + True + + Attributes: + label (str): the label. + """ + _value = None # type: Optional[int] + _cache_value = None # type: Optional[int] + label = None # type: Optional[str] + dependency = None # type: Optional[Dependency] + + @staticmethod + def to_int(x: Union[int, "Placeholder"]): + """ + Convert the placeholder into concrete integer value. + Args: + x: the placeholder + + Returns: + (int or Placeholder): If `x` is resolved, an integer is returned. Otherwise, `x` itself is returned. + """ + if isinstance(x, Placeholder): + return x.value if x.is_resolved else x + + else: + return int(x) + + @staticmethod + def force_int(x: Union[int, "Placeholder"]): + """ + Convert the placeholder into concrete integer value. If `x` is not resolved, an error is raised. + Args: + x: the placeholder + + Returns: + (int): an integer + """ + if isinstance(x, Placeholder): + if x.is_resolved: + return x.value + + raise ValueError(f"{x} is not resolved.") + else: + return int(x) + + @staticmethod + def check_resolved(x: Union[int, "Placeholder"]): + """ + Check whether specified placeholder is resolved or not. + Args: + x: the placeholder + + Returns: + (bool): If `True`, the placeholder is resolved. Otherwise, it's not resolved. + """ + if isinstance(x, Placeholder): + return x.is_resolved + + else: + return True + + @staticmethod + def _check_deep_equal(p1: Union[int, "Placeholder"], p2: Union[int, "Placeholder"]) -> bool: + if Placeholder.check_resolved(p1) and Placeholder.check_resolved(p2): + return Placeholder.force_int(p1) == Placeholder.force_int(p2) + + elif Placeholder.check_resolved(p1) or Placeholder.check_resolved(p2): + return False + + elif p1.dependency is not None and p2.dependency is not None: + return Dependency.check_deep_equal(p1.dependency, p2.dependency) + + else: + return p1.label == p2.label + + def __new__(cls, dependency: Optional[Dependency] = None, value: Union[int, "Placeholder"] = None, + label: str = None): + if isinstance(value, Placeholder): + return value + + return super().__new__(cls) + + def __init__(self, dependency: Optional[Dependency] = None, value: Union[int, "Placeholder"] = None, + label: Optional[str] = None): + global _id + + if self is value: + return + + self.dependency = dependency + if label is None: + self.label = f"placeholder_[{_id}]" + _id += 1 + else: + self.label = label + + if value is not None: + self.value = value + + @property + def value(self) -> Union[int, "Placeholder"]: + """ + The placeholder's value. If it's not resolved, the placeholder itself is returned. + + If the placeholder is already resolved, new value cannot be set, and it causes an error. + """ + if self.is_resolved: + if self.dependency: + if self._cache_value is None: + self._cache_value = self.dependency.value + + # noinspection PyTypeChecker + return self._cache_value + + else: + return self._value + + else: + return self + + @value.setter + def value(self, new_v: int): + if not (isinstance(new_v, int) or isinstance(new_v, np.int32) or isinstance(new_v, np.int64)): + raise TypeError(f"Placeholder#value must be a int, not '{type(new_v)}'") + + if self.is_resolved: + raise ValueError(f"{self} is already resolved") + + else: + # noinspection PyTypeChecker + self._value = int(new_v) + + @property + def is_resolved(self) -> bool: + """ + Return `True` if the placeholder is resolved. Otherwise `False` is returned. + """ + if self._value is not None or self._cache_value is not None: + return True + + elif self.dependency: + return self.dependency.is_resolved + + else: + return False + + def __add__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + other = Placeholder(value=other) + + if self.is_resolved and other.is_resolved: + return self.value + other.value + + if self.dependency: + if self.dependency.operator == PlaceholderOperator.Add: + return Placeholder(Dependency(PlaceholderOperator.Add, self.dependency.operands + [other])) + + return Placeholder(Dependency(PlaceholderOperator.Add, [self, other])) + + def __radd__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + # Commutative property + return self.__add__(other) + + def __sub__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + return self + (-1 * other) + + def __rsub__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + return (-1 * self) + other + + def __mul__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + other = Placeholder(value=other) + + if self.is_resolved and other.is_resolved: + return self.value * other.value + + if self.dependency: + if self.dependency.operator == PlaceholderOperator.Add or self.dependency.operator == PlaceholderOperator.Sub: + # (v0+v1)*o = v0*o + v1*o + return Placeholder(Dependency(self.dependency.operator, + [Placeholder(value=v * other) for v in self.dependency.operands])) + + elif self.dependency.operator == PlaceholderOperator.Mul: + return Placeholder(Dependency(PlaceholderOperator.Mul, self.dependency.operands + [other])) + + return Placeholder(Dependency(PlaceholderOperator.Mul, [self, other])) + + def __rmul__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + return self.__mul__(other) + + def __floordiv__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + other = Placeholder(value=other) + + if self.is_resolved and other.is_resolved: + return self.value // other.value + + return Placeholder(Dependency(PlaceholderOperator.FloorDiv, [self, other])) + + def __rfloordiv__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + other = Placeholder(value=other) + + if self.is_resolved and other.is_resolved: + return other.value // self.value + + return Placeholder(Dependency(PlaceholderOperator.FloorDiv, [other, self])) + + def __mod__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + other = Placeholder(value=other) + + if self.is_resolved and other.is_resolved: + return self.value % other.value + + return Placeholder(Dependency(PlaceholderOperator.Mod, [self, other])) + + def __rmod__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]: + other = Placeholder(value=other) + + if self.is_resolved and other.is_resolved: + return other.value % self.value + + return Placeholder(Dependency(PlaceholderOperator.Mod, [other, self])) + + def __int__(self): + return Placeholder.force_int(self) + + def __eq__(self, other: Union[int, "Placeholder"]) -> bool: + return Placeholder._check_deep_equal(self, other) + + def __ne__(self, other: Union[int, "Placeholder"]) -> bool: + return not Placeholder._check_deep_equal(self, other) + + def __gt__(self, other: Union[int, "Placeholder"]) -> bool: + if not self.is_resolved: + raise ValueError("First operand is unresolved placeholder. It can't be compared.") + + if not Placeholder.check_resolved(other): + raise ValueError("Second operand is unresolved placeholder. It can't be compared.") + + return self.value > Placeholder.force_int(other) + + def __lt__(self, other: Union[int, "Placeholder"]) -> bool: + if not self.is_resolved: + raise ValueError("First operand is unresolved placeholder. It can't be compared.") + + if not Placeholder.check_resolved(other): + raise ValueError("Second operand is unresolved placeholder. It can't be compared.") + + return self.value < Placeholder.force_int(other.value) + + def __ge__(self, other: Union[int, "Placeholder"]) -> bool: + if not self.is_resolved: + raise ValueError("First operand is unresolved placeholder. It can't be compared.") + + if not Placeholder.check_resolved(other): + raise ValueError("Second operand is unresolved placeholder. It can't be compared.") + + return self.value >= Placeholder.force_int(other) + + def __le__(self, other: Union[int, "Placeholder"]) -> bool: + if not self.is_resolved: + raise ValueError("First operand is unresolved placeholder. It can't be compared.") + + if not Placeholder.check_resolved(other): + raise ValueError("Second operand is unresolved placeholder. It can't be compared.") + + return self.value <= Placeholder.force_int(other) + + def __repr__(self): + if self.is_resolved: + return str(self.value) + + else: + if self.dependency: + return self.dependency.__repr__() + + else: + return f"<{self.label}>" if self.label else f"<{self.__class__.__name__} at {hex(id(self))}>" + + def __hash__(self): + return id(self) + + def dump(self): + if self.dependency: + return self.dependency.dump() + + elif self._value is not None: + return str(self._value) + + else: + return f"<{self.label}>" if self.label else f"<{self.__class__.__name__} at {hex(id(self))}>" + + def _to_serializable_(self): + if self.is_resolved: + return self.value + + else: + return { + "eval": self.generate_js_function() + } + + def get_depend_placeholders(self): + """ + List up all dependent placeholders + + Returns: + (list of Placeholder): list of all dependent placeholders + """ + if Placeholder.check_resolved(self): + return set() + + if self.dependency: + res = set() + for v in self.dependency.operands: + res.update(v.get_depend_placeholders()) + return res + + else: + return {self} + + def generate_js_function(self, flag_semicolon=True): + """ + Generate javascript code to resolve this placeholder's value at runtime. + + Args: + flag_semicolon(bool): If True, semicolon is appended into generated code. + + Returns: + (str): generated code + """ + if self.is_resolved: + return f"{self.value}" + (";" if flag_semicolon else "") + + else: + if self.dependency: + return self.dependency.generate_js_function() + + else: + return f"placeholders['{self.label}']" + (";" if flag_semicolon else "") diff --git a/src/graph_transpiler/webdnn/graph/shape.py b/src/graph_transpiler/webdnn/graph/shape.py new file mode 100644 index 000000000..65136d724 --- /dev/null +++ b/src/graph_transpiler/webdnn/graph/shape.py @@ -0,0 +1,52 @@ +import ast +from typing import Dict, Tuple, Union, List + +from webdnn.graph.placeholder import Placeholder +import re + +_reg_quote = re.compile("['\"]") +_reg_trail_space = re.compile("\s{2,}") +_reg_placeholder = re.compile("[a-zA-Z_]+") + + +def _normalize_text(text: str) -> str: + text = _reg_quote.sub(lambda ma: " ", text) + text = _reg_trail_space.sub(lambda ma: " ", text) + text = _reg_placeholder.sub(lambda ma: f"'{ma[0]}'" , text) + return text + + +class Shape: + @staticmethod + def parse(text: str) -> Tuple[List[Union[int, Placeholder]], Dict[str, Placeholder]]: + """ + Parse string and return shape + + Args: + text: string to specify shape + + Returns: + shape: parsed shape + placeholders: parsed placeholders + """ + normalized_text = _normalize_text(text) + tmp = ast.literal_eval(normalized_text) + shape = [] + placeholders = {} + for i, t in enumerate(tmp): + if isinstance(t, str): + pt = Placeholder(label=t) + placeholders[t] = pt + + elif isinstance(t, int): + pt = t + + else: + raise ValueError("[Shape.parse()] Invalid shape format. Each element of shape must be int or str:" + f"text='{text}', " + f"{normalized_text + ', ' if normalized_text != text else ''}" + f"type(shape[{i}])={type(t)}") + + shape.append(pt) + + return shape, placeholders diff --git a/src/graph_transpiler/webdnn/graph/traverse.py b/src/graph_transpiler/webdnn/graph/traverse.py index eb1a31800..e8f430457 100644 --- a/src/graph_transpiler/webdnn/graph/traverse.py +++ b/src/graph_transpiler/webdnn/graph/traverse.py @@ -1,10 +1,12 @@ -from typing import Type, List, Set, Iterable, Union, Tuple +from typing import Type, List, Set, Iterable, Union, Tuple, Optional from webdnn.graph.attribute import Attribute from webdnn.graph.graph import Graph from webdnn.graph.node import Node from webdnn.graph.operator import Operator from webdnn.graph.variable import Variable +from webdnn.graph.variables.constant_variable import ConstantVariable +from webdnn.util import console Query = Union[Type[Attribute], Type[Node]] @@ -57,44 +59,106 @@ def filter_nodes(nodes: Iterable[Node], query: Query, mode_not: bool = False) -> def listup_nodes(graph: Graph) -> List[Node]: - stack: List[Node] = list(graph.outputs) - result: List[Node] = [] - resolved: Set[Node] = set() + stack = list(graph.outputs) # type: List[Node] + stacked = set(stack) # type: Set[Node] + resolved = set() # type Set[Node] + result = [] # type: List[Node] while len(stack) > 0: - node = stack.pop(0) + node = stack.pop() if node in resolved: continue - unresolved = [d for d in node.prevs if (d is not None) and (d not in resolved)] + unresolved_prev = [d for d in node.prevs if (d is not None) and (d not in resolved)] + unstacked_next = [d for d in node.nexts if (d is not None) and (d not in stacked)] - if len(unresolved) > 0: - stack.insert(0, node) - for dependent in unresolved: - stack.insert(0, dependent) + if len(unstacked_next) != 0: + stack += unstacked_next + stacked.update(unstacked_next) - else: - result.append(node) + if len(unresolved_prev) == 0: resolved.add(node) + result.append(node) + + else: + stacked.update(unresolved_prev) + stack.append(node) + stack += unresolved_prev return result def listup_operators(graph: Graph) -> List[Operator]: - ops: List[Operator] = filter_nodes(listup_nodes(graph), Operator) + ops = filter_nodes(listup_nodes(graph), Operator) # type: List[Operator] return ops def listup_variables(graph: Graph) -> List[Variable]: - variables: List[Variable] = filter_nodes(listup_nodes(graph), Variable) + variables = filter_nodes(listup_nodes(graph), Variable) # type: List[Variable] return variables def dump(graph: Graph): indent = "" for op in listup_operators(graph): - print(f"---------------------------------------------------------------------------") - print(f"{indent}{op.__class__.__name__} : {op.name}") - print(f"{indent} In : {op.inputs}") - print(f"{indent} Out : {op.outputs}") - print(f"{indent} Attr: {[attr.__class__.__name__ for attr in op.attributes]}") + console.debug(f"---------------------------------------------------------------------------") + console.debug(f"{indent}{op.__class__.__name__} : {op.name}") + console.debug(f"{indent} In : {op.inputs}") + console.debug(f"{indent} Out : {op.outputs}") + console.debug(f"{indent} Attr: {[attr.__class__.__name__ for attr in op.attributes]}") + + +def dump_dot(graph: Graph, name: Optional[str] = None) -> str: + """ + Dumps graph into dot language for visualization. + + Args: + graph: Target graph + + Returns: + source code of dot language. + """ + dot_source = "" + dot_source += "digraph webdnn_ir {\n" + + # graph setting + dot_source += "graph [\n" + if name: + dot_source += f"label=\"{name}\"\n" + dot_source += "];\n" + + added_variables = set() + + def visualize_variable(var: Variable) -> str: + if var in added_variables: + return "" + node_attrs = {} + node_attrs["label"] = f"\"{var.name}\n{var.shape}\nOrder={var.order}\"" + if isinstance(var, ConstantVariable): + node_attrs["shape"] = "doubleoctagon" + else: + node_attrs["shape"] = "octagon" + if var in graph.inputs: + node_attrs["style"] = "\"dashed\"" + if var in graph.outputs: + node_attrs["style"] = "\"bold\"" + + dot_source_var = "" + dot_source_var += f"var_{id(var)} [\n" + dot_source_var += ",".join(f"{attr_key}={attr_value}" for attr_key, attr_value in node_attrs.items()) + dot_source_var += "];\n" + added_variables.add(var) + return dot_source_var + + for op in listup_operators(graph): + op_params = getattr(op, "parameters", {}) + op_params_str = "\n".join(f"{k}={v}" for k, v in op_params.items()) + dot_source += f"op_{op.name} [label=\"{op.name}\n{op.__class__.__name__}\n{op_params_str}\", shape=box];\n" + for connection_name, var in op.inputs.items(): + dot_source += visualize_variable(var) + dot_source += f"var_{id(var)} -> op_{op.name} [label=\"{connection_name}\"];\n" + for connection_name, var in op.outputs.items(): + dot_source += visualize_variable(var) + dot_source += f"op_{op.name} -> var_{id(var)} [label=\"{connection_name}\"];\n" + dot_source += "}" + return dot_source diff --git a/src/graph_transpiler/webdnn/graph/variable.py b/src/graph_transpiler/webdnn/graph/variable.py index eee7abd2f..3a71d6a23 100644 --- a/src/graph_transpiler/webdnn/graph/variable.py +++ b/src/graph_transpiler/webdnn/graph/variable.py @@ -1,33 +1,42 @@ -from typing import Iterable +from typing import Union, Dict, List, Set, Iterable -import numpy as np - -from webdnn.graph.interface import IVariable +from webdnn.graph import operator +from webdnn.graph.axis import Axis from webdnn.graph.node import Node from webdnn.graph.order import Order +from webdnn.graph.placeholder import Placeholder +from webdnn.util.misc import mul -# FIXME: DOCS -class Variable(Node, IVariable): +class Variable(Node): """ - レイヤー間で受け渡される変数 - 名前で識別される - 現在のところ、float32型(4byte/element)を想定している - shapeはlist[int]で、その順序はAttribute(OrderNC etc)に依存 + Variables input to / output from operators. + + Attrs: + shape (list of int or Placeholder): shape of the variable. + order (Order): Data order such as OrderNHWC, OrderNC, and so on. + input_to (set of Operator): operators to which this variable is input + output_from (Operator): operator which generates this variable """ + shape: List[Union[int, Placeholder]] + order: Order + input_to: Set["operator.Operator"] + output_from: "operator.Operator" - def __init__(self, shape: Iterable[int], order: Order): + def __init__(self, shape: Iterable[Union[int, Placeholder]], order: Order): super().__init__() - self.shape = list(int(v) for v in shape) + self.shape = list(shape) self.input_to = set() self.output_from = None self.order = order - assert self.order.ndim == len(self.shape) + assert self.order.ndim == len(self.shape), "[Variable] order and shape are mismatched:" + f"order={order}, shape={shape}" @property - def name(self): + def name(self) -> str: + """name of this variable""" return self.parameters["name"] if "name" in self.parameters else "" @name.setter @@ -35,32 +44,50 @@ def name(self, name: str): self.parameters["name"] = name @property - def size(self): - # noinspection PyTypeChecker - return int(np.prod(self.shape)) + def size(self) -> Union[int, Placeholder]: + """number of elements""" + return Placeholder.to_int(mul(self.shape)) @property def ndim(self): - return len(self.shape) + """number of dimension""" + return self.order.ndim @property - def shape_dict(self): + def shape_dict(self) -> Dict[Axis, Union[int, Placeholder]]: + """dictionary of axis and shape size pairs""" return dict(zip(self.order.axes, self.shape)) - def change_order(self, order: Order): - # 次元数を減らす時は、なくなる次元のサイズが1のときだけOK - # 増える次元は、サイズ1 + def change_order(self, order: Order) -> "Variable": + """Change variable order + + When number of dimension will be increased, axes whose size is one are created. + Conversely when number of dimension will be decreased, the size of axes which will be removed must be one. + + Args: + order: new order + """ current_shape_dict = self.shape_dict new_shape = [current_shape_dict.get(axis, 1) for axis in order.axes] for axis, size in current_shape_dict.items(): if axis not in order.axes: - assert size == 1 + if Placeholder.check_resolved(size): + assert size == 1, "[Variable.change_order()] The size of axes which will be removed must be one:" + f"variable={self}, shape_dict[{axis}]={size}." self.order = order self.shape = new_shape + return self + + def replace(self, new_variable: "Variable"): + self.output_from.replace_output(self, new_variable) + + for op in list(self.input_to): # type: operator.Operator + op.replace_input(self, new_variable) + def __repr__(self): order_repr = ''.join(map(lambda e: e.name, self.order.axes)) - return f"" + return f"<{self.__class__.__name__} {self.name} shape={self.shape}, order=\"{order_repr}\">" def __str__(self): return self.__repr__() diff --git a/src/graph_transpiler/webdnn/graph/variables/constant_variable.py b/src/graph_transpiler/webdnn/graph/variables/constant_variable.py index 4fb7ce925..479c5635f 100644 --- a/src/graph_transpiler/webdnn/graph/variables/constant_variable.py +++ b/src/graph_transpiler/webdnn/graph/variables/constant_variable.py @@ -7,6 +7,12 @@ # FIXME: DOCS class ConstantVariable(Variable): + """ + Constant variable + + attrs: + data (np.array) : data of the variable + """ data: np.array def __init__(self, data: np.array, order: Order): @@ -14,26 +20,26 @@ def __init__(self, data: np.array, order: Order): self.data = data self.attributes = {Constant(self)} - def __repr__(self): - order_repr = ''.join(map(lambda e: e.name, self.order.axes)) - return f"" - - def change_order(self, order: Order): - # 次元数を減らす時は、なくなる次元のサイズが1のときだけOK - # 増える次元は、サイズ1 - current_shape_dict = self.shape_dict - new_shape = [current_shape_dict.get(axis, 1) for axis in order.axes] - for axis, size in current_shape_dict.items(): - if axis not in order.axes: - assert size == 1 - - if len(self.order.axes) == len(order.axes): - # 新しい軸がもとの軸で何番目かを列挙 - trans_axes = tuple(self.order.axes_dict[axis] for axis in order.axes) + def change_order(self, order: Order) -> "ConstantVariable": + """Change variable order + + When number of dimension will be increased, axes whose size is one are created. + Conversely when number of dimension will be decreased, the size of axes which will be removed must be one. + + Args: + order: new order + """ + old_order = self.order + + super().change_order(order) + + new_order = self.order + + if set(old_order.axes) == set(new_order.axes): + trans_axes = tuple(old_order.axes_dict[axis] for axis in new_order.axes) self.data = np.transpose(self.data, trans_axes) + else: - # 別に実装できないわけではないが手抜き - raise NotImplementedError() + raise NotImplementedError("[ConstantVariable.change_order] Currently, it's not supported to increase or decrease axis") # FIXME - self.order = order - self.shape = new_shape + return self diff --git a/src/graph_transpiler/webdnn/graph/converters/__init__.py b/src/graph_transpiler/webdnn/optimizer/__init__.py similarity index 100% rename from src/graph_transpiler/webdnn/graph/converters/__init__.py rename to src/graph_transpiler/webdnn/optimizer/__init__.py diff --git a/src/graph_transpiler/webdnn/optimizer/general_optimize_rule.py b/src/graph_transpiler/webdnn/optimizer/general_optimize_rule.py new file mode 100644 index 000000000..ff9b289e6 --- /dev/null +++ b/src/graph_transpiler/webdnn/optimizer/general_optimize_rule.py @@ -0,0 +1,15 @@ +from webdnn.optimizer.sub_rules.concat_affine import ConcatAffine +from webdnn.optimizer.sub_rules.concat_scalar_affine import ConcatScalarAffine +from webdnn.optimizer.sub_rules.remove_last_softmax import RemoveLastSoftmax +from webdnn.optimizer.sub_rules.concat_zero_padding import ConcatZeroPadding +from webdnn.graph.optimize_rule import OptimizeRule + + +class GeneralOptimizeRule(OptimizeRule): + def __init__(self): + super(GeneralOptimizeRule, self).__init__() + + self.register(RemoveLastSoftmax()) + self.register(ConcatAffine()) + self.register(ConcatScalarAffine()) + self.register(ConcatZeroPadding()) diff --git a/src/graph_transpiler/webdnn/backend/webgpu/injectors/buffer_position_injector.py b/src/graph_transpiler/webdnn/optimizer/sub_rules/__init__.py similarity index 100% rename from src/graph_transpiler/webdnn/backend/webgpu/injectors/buffer_position_injector.py rename to src/graph_transpiler/webdnn/optimizer/sub_rules/__init__.py diff --git a/src/graph_transpiler/webdnn/frontend/sub_rules/concat_affine.py b/src/graph_transpiler/webdnn/optimizer/sub_rules/concat_affine.py similarity index 100% rename from src/graph_transpiler/webdnn/frontend/sub_rules/concat_affine.py rename to src/graph_transpiler/webdnn/optimizer/sub_rules/concat_affine.py diff --git a/src/graph_transpiler/webdnn/frontend/sub_rules/concat_scalar_affine.py b/src/graph_transpiler/webdnn/optimizer/sub_rules/concat_scalar_affine.py similarity index 100% rename from src/graph_transpiler/webdnn/frontend/sub_rules/concat_scalar_affine.py rename to src/graph_transpiler/webdnn/optimizer/sub_rules/concat_scalar_affine.py diff --git a/src/graph_transpiler/webdnn/optimizer/sub_rules/concat_zero_padding.py b/src/graph_transpiler/webdnn/optimizer/sub_rules/concat_zero_padding.py new file mode 100644 index 000000000..9f54c7c68 --- /dev/null +++ b/src/graph_transpiler/webdnn/optimizer/sub_rules/concat_zero_padding.py @@ -0,0 +1,49 @@ +from typing import Tuple, Union + +from webdnn.graph.graph import Graph +from webdnn.graph.operators.zero_padding_2d import ZeroPadding2D +from webdnn.graph.operators.convolution2d import Convolution2D +from webdnn.graph.operators.max_pooling_2d import MaxPooling2D +from webdnn.graph.operators.average_pooling_2d import AveragePooling2D +from webdnn.graph.optimize_rule import OptimizeRule +from webdnn.graph.traverse import search_sub_structure +from webdnn.graph.variable import Variable +from webdnn.util import flags + + +class ConcatZeroPadding(OptimizeRule): + def optimize(self, graph: Graph) -> Tuple[Graph, bool]: + """ + Merges padding of ZeroPadding2D and Convolution2D | MaxPooling2D | AveragePooling2D layer + Args: + graph: + + Returns: + + """ + # this optimization is always applied (since backends do not implement padding) + flag_changed = False + + for tail_layer in [Convolution2D, MaxPooling2D, AveragePooling2D]: + matches = search_sub_structure(graph, [ZeroPadding2D, Variable, tail_layer]) + while len(matches) > 0: + match = matches[0] + a1: ZeroPadding2D = match[0] + a2: Union[Convolution2D, MaxPooling2D, AveragePooling2D] = match[2] + + zero_pad = a1.parameters["padding"] + conv_pad = a2.parameters["padding"] + a2.parameters["padding"] = (zero_pad[0] + conv_pad[0], zero_pad[1] + conv_pad[1]) + + x1 = a1.inputs["x"] + x2 = a2.inputs["x"] + + a1.remove_all() + # replace_input checks if the shape of x1 and x2 are same, but this restriction does not hold. + a2.remove_input(x2) + a2.append_input("x", x1) + + flag_changed = True + matches = search_sub_structure(graph, [ZeroPadding2D, Variable, tail_layer]) + + return graph, flag_changed diff --git a/src/graph_transpiler/webdnn/frontend/sub_rules/remove_last_softmax.py b/src/graph_transpiler/webdnn/optimizer/sub_rules/remove_last_softmax.py similarity index 100% rename from src/graph_transpiler/webdnn/frontend/sub_rules/remove_last_softmax.py rename to src/graph_transpiler/webdnn/optimizer/sub_rules/remove_last_softmax.py diff --git a/src/graph_transpiler/webdnn/util/console.py b/src/graph_transpiler/webdnn/util/console.py new file mode 100644 index 000000000..3a6b8b5e6 --- /dev/null +++ b/src/graph_transpiler/webdnn/util/console.py @@ -0,0 +1,55 @@ +import shutil +import sys +from typing import List, Any + +from webdnn.util import flags + +ESC = "\x1b" + + +class Color: + Black = 0 + Red = 1 + Green = 2 + Yellow = 3 + Blue = 4 + Magenta = 5 + Cyan = 6 + White = 7 + + +def colorize(text: str, color: int, bright: bool = False): + return f"{ESC}[{'1' if bright else '0'};3{color}m{text}{ESC}[0;39m" + + +def warning(*texts: Any): + stderr(colorize("".join(map(str, texts)), Color.Yellow)) + + +def error(*texts: Any): + stderr(colorize("".join(map(str, texts)), Color.Red)) + + +def debug(*texts: Any): + if flags.DEBUG: + stderr(colorize("[DEBUG]", Color.Blue) + " " + "".join(map(str, texts))) + + +def info(*texts: Any): + return colorize("".join(map(str, texts)), Color.Green) + + +def get_size(): + return shutil.get_terminal_size() + + +def get_width(): + return get_size()[0] + + +def get_height(): + return get_size()[1] + + +def stderr(*texts: Any): + sys.stderr.write("".join(map(str, texts)) + "\n") diff --git a/src/graph_transpiler/webdnn/util/flags/optimize.py b/src/graph_transpiler/webdnn/util/flags/optimize.py index 002486c57..464b6eef3 100644 --- a/src/graph_transpiler/webdnn/util/flags/optimize.py +++ b/src/graph_transpiler/webdnn/util/flags/optimize.py @@ -15,6 +15,3 @@ VALIDATE_GENERATED_SOURCE = os.environ.get("VALIDATE_GENERATED_SOURCE", "1") == "1" OPTIMIZE_INPLACE_OPERATION = OPTIMIZE and os.environ.get("OPTIMIZE_INPLACE_OPERATION", "1") == "1" OPTIMIZE_MEMORY_ALLOCATION = OPTIMIZE and os.environ.get("OPTIMIZE_MEMORY_ALLOCATION", "1") == "1" - -# kernel generation -EMBED_METABUFFER_VALUE = OPTIMIZE and os.environ.get("EMBED_METABUFFER_VALUE", "0") == "1" diff --git a/src/graph_transpiler/webdnn/util/json/json.py b/src/graph_transpiler/webdnn/util/json/json.py index c27af380a..1bde75ce1 100644 --- a/src/graph_transpiler/webdnn/util/json/json.py +++ b/src/graph_transpiler/webdnn/util/json/json.py @@ -1,6 +1,8 @@ from abc import abstractmethod from json import dump as original_dump, dumps as original_dumps, JSONEncoder +import numpy as np + class SerializableMixin: @abstractmethod @@ -14,6 +16,15 @@ def default(self, obj): # noinspection PyProtectedMember return obj._to_serializable_() + if isinstance(obj, np.int64) or isinstance(obj, np.int32) or isinstance(obj, np.int16) or isinstance(obj, np.int8) or \ + isinstance(obj, np.uint32) or isinstance(obj, np.uint16) or isinstance(obj, np.uint8): + # noinspection PyTypeChecker + return int(obj) + + if isinstance(obj, np.float64) or isinstance(obj, np.float32) or isinstance(obj, np.float16): + # noinspection PyTypeChecker + return float(obj) + return JSONEncoder.default(self, obj) diff --git a/src/graph_transpiler/webdnn/util/misc.py b/src/graph_transpiler/webdnn/util/misc.py new file mode 100644 index 000000000..74c908ba2 --- /dev/null +++ b/src/graph_transpiler/webdnn/util/misc.py @@ -0,0 +1,6 @@ +from functools import reduce +from typing import Iterable + + +def mul(iterable: Iterable, start=1, func=lambda x, y: x * y): + return reduce(func, iterable, start) diff --git a/test/README.md b/test/README.md index 903f882b5..ccff2d67d 100644 --- a/test/README.md +++ b/test/README.md @@ -2,34 +2,34 @@ ### GraphBuilder -- テストフレームワークとして[nose](http://nose.readthedocs.io/en/latest/)を使用 - `pip install nose` でインストールされる `nosetests` コマンドでテストを実行可能。 +- Test framework is [nose](http://nose.readthedocs.io/en/latest/) + `nosetests` installed by `pip install nose` runs the test。 ``` nosetests ``` -- テスト追加の際はフォルダ・ファイル名の末尾に `_test` をつけること +- The suffix `_test` must be attached to the file / folder name when adding a test -- カバレッジも取れる +- Taking coverage ``` nosetests --with-coverage --cover-tests graph_transpiler ``` - ただしカバレッジはそもそもimportしていないファイルについて計算されないので注意 + Coverage is not calculated for files which are not imported. -- カーネルコード生成のテストは、ウェブブラウザで行う。 +- Kernel code generation test is done via a web browser. - - 予め、テスト用のカーネルコードを生成する必要がある + - Kernel code have to be generated in advance. ``` - nosetests -w ./test/kernels + nosetests -w ./test/runtime/operators_test ``` - - `kernel_test.html` を開いて[RUN]を押すと生成されたカーネルコードがテストされる。 + - Opening `kernel_test.html` and press [RUN] to test the generated kernel code. ### DescriptorRunner -- 未定 +- Not implemented diff --git a/test/kernel_test.html b/test/kernel_test.html deleted file mode 100644 index 85a2baf42..000000000 --- a/test/kernel_test.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - Kernel Test - - -

Kernel Test

-

- Click RUN button and check console logs. -

- -
- - -
- - - - - - - - - diff --git a/test/kernels/average_pooling_2d_test.py b/test/kernels/average_pooling_2d_test.py deleted file mode 100644 index cd514887b..000000000 --- a/test/kernels/average_pooling_2d_test.py +++ /dev/null @@ -1,38 +0,0 @@ -import itertools - -import numpy as np - -from test.util import generate_kernel_test_case -from webdnn.graph.graph import Graph -from webdnn.graph.operators.average_pooling_2d import AveragePooling2D -from webdnn.graph.order import OrderNHWC -from webdnn.graph.variable import Variable - - -def test_general(): - vx = np.random.rand(2, 4, 6, 8) - vy = np.empty((2, 2, 3, 8)) - - for n, h2, w2, c in itertools.product(range(vy.shape[0]), - range(vy.shape[1]), - range(vy.shape[2]), - range(vy.shape[3])): - v = 0 - for (kh, kw) in itertools.product(range(3), range(3)): - h1 = (h2 * 2 - 1) + kh - w1 = (w2 * 2 - 1) + kw - - v += 0 if (h1 < 0 or h1 >= 4 or w1 < 0 or w1 >= 6) else vx[n, h1, w1, c] - - vy[n, h2, w2, c] = v / (3 * 3) - - x = Variable(vx.shape, order=OrderNHWC) - y, = AveragePooling2D(None, ksize=3, padding=1, stride=2)(x) - - generate_kernel_test_case( - description=f"Average Pooling", - backend=["webgpu", "webassembly", "fallback"], - graph=Graph([x], [y]), - inputs={x: vx}, - expected={y: vy} - ) diff --git a/test/kernels/linear_test.py b/test/kernels/linear_test.py deleted file mode 100644 index 8981c6861..000000000 --- a/test/kernels/linear_test.py +++ /dev/null @@ -1,84 +0,0 @@ -import numpy as np - -from test.util import generate_kernel_test_case -from webdnn.graph.graph import Graph -from webdnn.graph.operators.linear import Linear -from webdnn.graph.order import OrderNC, OrderCN, OrderHWCN, OrderNHWC -from webdnn.graph.variable import Variable -from webdnn.graph.variables.constant_variable import ConstantVariable - - -def test_NC_CN(): - vx = np.random.rand(3, 4) - vw = np.random.rand(4, 5) - vy = np.dot(vx, vw) - - x = Variable(vx.shape, order=OrderNC) - w = ConstantVariable(vw, order=OrderCN) - y1, = Linear(None)(x, w) - y2, = Linear(None)(x, w) - y3, = Linear(None)(x, w) - - generate_kernel_test_case( - description=f"Linear: NC*CN", - backend=["fallback"], - graph=Graph([x], [y1]), - inputs={x: vx}, - expected={y1: vy}, - raise_skip=False - ) - - generate_kernel_test_case( - description=f"Linear: NC*CN", - backend=["webgpu"], - graph=Graph([x], [y2]), - inputs={x: vx}, - expected={y2: vy}, - raise_skip=False - ) - - generate_kernel_test_case( - description=f"Linear: NC*CN", - backend="webassembly", - graph=Graph([x], [y3]), - inputs={x: vx}, - expected={y3: vy} - ) - - -def test_NHWC_HWCN(): - vx = np.random.rand(2, 3, 4, 5) - vw = np.random.rand(3, 4, 5, 2) - vy = np.tensordot(vx, vw, ((1, 2, 3), (0, 1, 2))) - - x = Variable(vx.shape, order=OrderNHWC) - w = ConstantVariable(vw, order=OrderHWCN) - y1, = Linear(None)(x, w) - y2, = Linear(None)(x, w) - y3, = Linear(None)(x, w) - - generate_kernel_test_case( - description=f"Linear: NHWC*HWCN", - backend=["fallback"], - graph=Graph([x], [y1]), - inputs={x: vx}, - expected={y1: vy}, - raise_skip=False - ) - - generate_kernel_test_case( - description=f"Linear: NHWC*HWCN", - backend=["webgpu"], - graph=Graph([x], [y2]), - inputs={x: vx}, - expected={y2: vy}, - raise_skip=False - ) - - generate_kernel_test_case( - description=f"Linear: NHWC*HWCN", - backend="webassembly", - graph=Graph([x], [y3]), - inputs={x: vx}, - expected={y3: vy} - ) diff --git a/test/kernels/max_pooling_2d_test.py b/test/kernels/max_pooling_2d_test.py deleted file mode 100644 index 2a275e053..000000000 --- a/test/kernels/max_pooling_2d_test.py +++ /dev/null @@ -1,38 +0,0 @@ -import itertools - -import numpy as np - -from test.util import generate_kernel_test_case -from webdnn.graph.graph import Graph -from webdnn.graph.operators.max_pooling_2d import MaxPooling2D -from webdnn.graph.order import OrderNHWC -from webdnn.graph.variable import Variable - - -def test_general(): - vx = np.random.rand(2, 4, 6, 8) - vy = np.empty((2, 3, 4, 8)) - - for n, h2, w2, c in itertools.product(range(vy.shape[0]), - range(vy.shape[1]), - range(vy.shape[2]), - range(vy.shape[3])): - v = -float("Infinity") - for (kh, kw) in itertools.product(range(3), range(3)): - h1 = (h2 * 2 - 1) + kh - w1 = (w2 * 2 - 1) + kw - - v = max(v, 0 if (h1 < 0 or h1 >= 4 or w1 < 0 or w1 >= 6) else vx[n, h1, w1, c]) - - vy[n, h2, w2, c] = v - - x = Variable(vx.shape, order=OrderNHWC) - y, = MaxPooling2D(None, ksize=3, padding=1, stride=2)(x) - - generate_kernel_test_case( - description=f"Max Pooling", - backend=["webgpu", "webassembly", "fallback"], - graph=Graph([x], [y]), - inputs={x: vx}, - expected={y: vy} - ) diff --git a/test/runtime/frontend_test/chainer_test/add_constant_test.py b/test/runtime/frontend_test/chainer_test/add_constant_test.py new file mode 100644 index 000000000..40af80636 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/add_constant_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = vx + 3 + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.AddConstant", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/add_test.py b/test/runtime/frontend_test/chainer_test/add_test.py new file mode 100644 index 000000000..fc6130d84 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/add_test.py @@ -0,0 +1,46 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx1 = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vx2 = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = vx1 + vx2 + + graph = ChainerConverter().convert_from_inout_vars([vx1, vx2], [vy]) + + x1 = graph.inputs[0] + x2 = graph.inputs[1] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.Add", + graph=graph, + inputs={ + x1: ConstantVariable(vx1.data, OrderNCHW).change_order(x1.order).data, + x2: ConstantVariable(vx2.data, OrderNCHW).change_order(x2.order).data + }, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_add_itself(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = vx + vx + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.Add (y=x+x)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/average_pooling_2d_test.py b/test/runtime/frontend_test/chainer_test/average_pooling_2d_test.py new file mode 100644 index 000000000..fd69057a3 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/average_pooling_2d_test.py @@ -0,0 +1,75 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.average_pooling_2d(vx, ksize=2, stride=2) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.average_pooling_2d(ksize=2, stride=2, padding=0)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_padding_not_zero(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.average_pooling_2d(vx, ksize=2, stride=2, pad=1) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.average_pooling_2d(ksize=2, stride=2, padding=1)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_stride_is_none(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.average_pooling_2d(vx, ksize=2, stride=None, pad=1) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.average_pooling_2d(ksize=2, stride=None(=ksize), padding=1)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_irregular_size(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.average_pooling_2d(vx, ksize=(3, 4), stride=(1, 2), pad=(1, 3)) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.average_pooling_2d(ksize=(3,4), stride=(1,2), padding=(1,3))", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/batch_normalization_test.py b/test/runtime/frontend_test/chainer_test/batch_normalization_test.py new file mode 100644 index 000000000..1f6122653 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/batch_normalization_test.py @@ -0,0 +1,53 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test_BN_test(): + link = chainer.links.BatchNormalization(size=4) + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + + if chainer.__version__ >= "2.": + with chainer.using_config('train', False): + vy = link(vx) + else: + vy = link(vx, test=True) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.BatchNormalization test=True", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_BN_train(): + link = chainer.links.BatchNormalization(size=4) + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + + if chainer.__version__ >= "2.": + with chainer.using_config('train', False): + vy = link(vx) + else: + vy = link(vx, test=True) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.BatchNormalization test=False", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/clipped_relu_test.py b/test/runtime/frontend_test/chainer_test/clipped_relu_test.py new file mode 100644 index 000000000..5e718a54a --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/clipped_relu_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.clipped_relu(vx, z=0.5) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.clipped_relu", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/convolution_2d_test.py b/test/runtime/frontend_test/chainer_test/convolution_2d_test.py new file mode 100644 index 000000000..a7294373c --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/convolution_2d_test.py @@ -0,0 +1,45 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + link = chainer.links.Convolution2D(4, 10, ksize=3, stride=1, pad=1) + + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.Convolution2D", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_nobias(): + link = chainer.links.Convolution2D(4, 10, ksize=3, stride=1, pad=1, nobias=True) + + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.Convolution2D(nobias=True)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/deconvolution_2d_test.py b/test/runtime/frontend_test/chainer_test/deconvolution_2d_test.py new file mode 100644 index 000000000..ebd0afb1b --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/deconvolution_2d_test.py @@ -0,0 +1,47 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + link = chainer.links.Deconvolution2D(4, 10, ksize=3, stride=1, pad=1) + + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.Deconvolution2D", + graph=graph, + backend=["webgpu", "webassembly"], + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_nobias(): + link = chainer.links.Deconvolution2D(4, 10, ksize=3, stride=1, pad=1, nobias=True) + + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.Deconvolution2D(nobias=True)", + graph=graph, + backend=["webgpu", "webassembly"], + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/dilated_convolution_2d_test.py b/test/runtime/frontend_test/chainer_test/dilated_convolution_2d_test.py new file mode 100644 index 000000000..eae750694 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/dilated_convolution_2d_test.py @@ -0,0 +1,64 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test_dilate_is_1(): + link = chainer.links.DilatedConvolution2D(4, 10, ksize=3, stride=1, pad=1, dilate=1) + + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.DilatedConvolution2D(dilate=1)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_dilate_is_2(): + link = chainer.links.DilatedConvolution2D(4, 10, ksize=3, stride=1, pad=1, dilate=2) + + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.DilatedConvolution2D(dilate=2)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_nobias(): + link = chainer.links.DilatedConvolution2D(4, 10, ksize=3, stride=1, pad=1, dilate=2, nobias=True) + + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.DilatedConvolution2D(dilate=2, nobias=True)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/elu_test.py b/test/runtime/frontend_test/chainer_test/elu_test.py new file mode 100644 index 000000000..ba73869a7 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/elu_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.elu(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.elu", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/hard_sigmoid_test.py b/test/runtime/frontend_test/chainer_test/hard_sigmoid_test.py new file mode 100644 index 000000000..5ff034bd8 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/hard_sigmoid_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.hard_sigmoid(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.hard_sigmoid", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/linear_test.py b/test/runtime/frontend_test/chainer_test/linear_test.py new file mode 100644 index 000000000..df42a2f9a --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/linear_test.py @@ -0,0 +1,64 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW, OrderNC +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test_2d(): + link = chainer.links.Linear(4, 10) + + vx = chainer.Variable(np.random.rand(2, 4).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.Linear(input is 2D tensor)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNC).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNC).change_order(y.order).data} + ) + + +def test_4d(): + link = chainer.links.Linear(4 * 6 * 8, 10) + + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.Linear(input is 4D tensor)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNC).change_order(y.order).data} + ) + + +def test_nobias(): + link = chainer.links.Linear(4 * 6 * 8, 10, nobias=True) + + vx = chainer.Variable(np.random.rand(2, 4, 6, 8).astype(np.float32)) + vy = link(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] L.Linear(nobias=True)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNC).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/local_response_normalization_test.py b/test/runtime/frontend_test/chainer_test/local_response_normalization_test.py new file mode 100644 index 000000000..bafa7ec0e --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/local_response_normalization_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.local_response_normalization(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.local_response_normalization", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/max_pooling_2d_test.py b/test/runtime/frontend_test/chainer_test/max_pooling_2d_test.py new file mode 100644 index 000000000..8cd74c68c --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/max_pooling_2d_test.py @@ -0,0 +1,75 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.max_pooling_2d(vx, ksize=2, stride=2) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.average_pooling_2d(ksize=2, stride=2, padding=0)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_padding_not_zero(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.max_pooling_2d(vx, ksize=2, stride=2, pad=1) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.average_pooling_2d(ksize=2, stride=2, padding=1)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_stride_is_none(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.max_pooling_2d(vx, ksize=2, stride=None, pad=1) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.average_pooling_2d(ksize=2, stride=None(=ksize), padding=1)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) + + +def test_irregular_size(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.max_pooling_2d(vx, ksize=(3, 4), stride=(1, 2), pad=(1, 3)) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.max_pooling_2d(ksize=(3,4), stride=(1,2), padding=(1,3))", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/mul_constant_test.py b/test/runtime/frontend_test/chainer_test/mul_constant_test.py new file mode 100644 index 000000000..185d7c620 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/mul_constant_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = vx * 3 + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.MulConstant", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/relu_test.py b/test/runtime/frontend_test/chainer_test/relu_test.py new file mode 100644 index 000000000..fcfdced25 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/relu_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.relu(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.relu", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/sigmoid_test.py b/test/runtime/frontend_test/chainer_test/sigmoid_test.py new file mode 100644 index 000000000..a1a6ad624 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/sigmoid_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.sigmoid(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.sigmoid", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/softmax_test.py b/test/runtime/frontend_test/chainer_test/softmax_test.py new file mode 100644 index 000000000..2ea8e3930 --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/softmax_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNC +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test_middle_axis(): + vx = chainer.Variable(np.random.rand(2, 4)) + vy = chainer.functions.softmax(vx, axis=1) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.softmax(axis=1)", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNC).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNC).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/softplus_test.py b/test/runtime/frontend_test/chainer_test/softplus_test.py new file mode 100644 index 000000000..dbbe6fecf --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/softplus_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.softplus(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.softplus", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/frontend_test/chainer_test/tanh_test.py b/test/runtime/frontend_test/chainer_test/tanh_test.py new file mode 100644 index 000000000..b8a256e5b --- /dev/null +++ b/test/runtime/frontend_test/chainer_test/tanh_test.py @@ -0,0 +1,24 @@ +import chainer +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.frontend.chainer import ChainerConverter +from webdnn.graph.order import OrderNCHW +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test(): + vx = chainer.Variable(np.random.rand(2, 4, 6, 8)) + vy = chainer.functions.tanh(vx) + + graph = ChainerConverter().convert_from_inout_vars([vx], [vy]) + + x = graph.inputs[0] + y = graph.outputs[0] + + generate_kernel_test_case( + description=f"[chainer] F.tanh", + graph=graph, + inputs={x: ConstantVariable(vx.data, OrderNCHW).change_order(x.order).data}, + expected={y: ConstantVariable(vy.data, OrderNCHW).change_order(y.order).data} + ) diff --git a/test/runtime/operators_test/average_pooling_2d_test.py b/test/runtime/operators_test/average_pooling_2d_test.py new file mode 100644 index 000000000..3241ea92a --- /dev/null +++ b/test/runtime/operators_test/average_pooling_2d_test.py @@ -0,0 +1,73 @@ +import itertools + +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.average_pooling_2d import AveragePooling2D +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +def test_general(): + vx = np.random.rand(2, 4, 6, 8) + vy = np.empty((2, 2, 3, 8)) + KH, KW = (2, 2) + SH, SW = (2, 2) + PH, PW = (0, 0) + + for n, h2, w2, c in itertools.product(range(vy.shape[0]), + range(vy.shape[1]), + range(vy.shape[2]), + range(vy.shape[3])): + v = 0 + for (kh, kw) in itertools.product(range(KH), range(KW)): + h1 = (h2 * SH - PH) + kh + w1 = (w2 * SW - PW) + kw + + v += 0 if (h1 < 0 or h1 >= 4 or w1 < 0 or w1 >= 6) else vx[n, h1, w1, c] + + vy[n, h2, w2, c] = v / (KH * KW) + + x = Variable(vx.shape, order=OrderNHWC) + y, = AveragePooling2D(None, ksize=(KH, KW), stride=(SH, SW), padding=(PH, PW))(x) + + generate_kernel_test_case( + description=f"Average Pooling", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) + + +def test_irregular_size(): + vx = np.random.rand(2, 4, 6, 8) + vy = np.empty((2, 4, 5, 8)) + KH, KW = (3, 4) + SH, SW = (1, 2) + PH, PW = (1, 3) + + for n, h2, w2, c in itertools.product(range(vy.shape[0]), + range(vy.shape[1]), + range(vy.shape[2]), + range(vy.shape[3])): + v = 0 + for (kh, kw) in itertools.product(range(KH), range(KW)): + h1 = (h2 * SH - PH) + kh + w1 = (w2 * SW - PW) + kw + + v += 0 if (h1 < 0 or h1 >= 4 or w1 < 0 or w1 >= 6) else vx[n, h1, w1, c] + + vy[n, h2, w2, c] = v / (KH * KW) + + x = Variable(vx.shape, order=OrderNHWC) + y, = AveragePooling2D(None, ksize=(KH, KW), stride=(SH, SW), padding=(PH, PW))(x) + + generate_kernel_test_case( + description=f"Average Pooling with irregular window size", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/kernels/axiswise_bias_test.py b/test/runtime/operators_test/axiswise_bias_test.py similarity index 75% rename from test/kernels/axiswise_bias_test.py rename to test/runtime/operators_test/axiswise_bias_test.py index fc0df45d6..dc7b71071 100644 --- a/test/kernels/axiswise_bias_test.py +++ b/test/runtime/operators_test/axiswise_bias_test.py @@ -6,7 +6,6 @@ from webdnn.graph.operators.axiswise_bias import AxiswiseBias from webdnn.graph.order import OrderC, OrderNHWC, OrderNCHW, OrderCNHW from webdnn.graph.variable import Variable -from webdnn.graph.variables.constant_variable import ConstantVariable def test_minor_axis(): @@ -15,14 +14,14 @@ def test_minor_axis(): vy = vx + vb[None, None, None, :] x = Variable(vx.shape, order=OrderNHWC) - b = ConstantVariable(vb, order=OrderC) + b = Variable(vb.shape, order=OrderC) y, = AxiswiseBias(None, axis=Axis.C)(x, b) generate_kernel_test_case( description=f"AxiswiseBias for minor axis", backend=["webgpu", "webassembly", "fallback"], - graph=Graph([x], [y]), - inputs={x: vx}, + graph=Graph([x, b], [y]), + inputs={x: vx, b: vb}, expected={y: vy} ) @@ -33,14 +32,14 @@ def test_middle_axis(): vy = vx + vb[None, :, None, None] x = Variable(vx.shape, order=OrderNCHW) - b = ConstantVariable(vb, order=OrderC) + b = Variable(vb.shape, order=OrderC) y, = AxiswiseBias(None, axis=Axis.C)(x, b) generate_kernel_test_case( description=f"AxiswiseBias for middle axis", - backend=["webgpu", "fallback"], - graph=Graph([x], [y]), - inputs={x: vx}, + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x, b], [y]), + inputs={x: vx, b: vb}, expected={y: vy} ) @@ -51,14 +50,14 @@ def test_major_axis(): vy = vx + vb[:, None, None, None] x = Variable(vx.shape, order=OrderCNHW) - b = ConstantVariable(vb, order=OrderC) + b = Variable(vb.shape, order=OrderC) y, = AxiswiseBias(None, axis=Axis.C)(x, b) generate_kernel_test_case( description=f"AxiswiseBias for major axis", - backend=["webgpu", "fallback"], - graph=Graph([x], [y]), - inputs={x: vx}, + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x, b], [y]), + inputs={x: vx, b: vb}, expected={y: vy} ) @@ -69,7 +68,7 @@ def test_mix_order(): vy = vx + vb[:, None, None, None] x = Variable(vx.shape, order=OrderCNHW) - b = ConstantVariable(vb, order=OrderC) + b = Variable(vb.shape, order=OrderC) y, = AxiswiseBias(None, axis=Axis.C)(x, b) x.change_order(OrderNHWC) @@ -77,8 +76,8 @@ def test_mix_order(): generate_kernel_test_case( description=f"AxiswiseBias for mix order", - backend=["webgpu"], - graph=Graph([x], [y]), - inputs={x: vx}, + backend=["webgpu", "webassembly"], + graph=Graph([x, b], [y]), + inputs={x: vx, b: vb}, expected={y: vy} ) diff --git a/test/kernels/axiswise_scale_test.py b/test/runtime/operators_test/axiswise_scale_test.py similarity index 72% rename from test/kernels/axiswise_scale_test.py rename to test/runtime/operators_test/axiswise_scale_test.py index 67c262c89..a177afbe7 100644 --- a/test/kernels/axiswise_scale_test.py +++ b/test/runtime/operators_test/axiswise_scale_test.py @@ -4,9 +4,8 @@ from webdnn.graph.axis import Axis from webdnn.graph.graph import Graph from webdnn.graph.operators.axiswise_scale import AxiswiseScale -from webdnn.graph.order import OrderC, OrderNHWC, OrderCNHW, OrderNCHW +from webdnn.graph.order import OrderC, OrderNHWC, OrderNCHW, OrderCNHW from webdnn.graph.variable import Variable -from webdnn.graph.variables.constant_variable import ConstantVariable def test_minor_axis(): @@ -15,14 +14,14 @@ def test_minor_axis(): vy = vx * vs[None, None, None, :] x = Variable(vx.shape, order=OrderNHWC) - s = ConstantVariable(vs, order=OrderC) + s = Variable(vs.shape, order=OrderC) y, = AxiswiseScale(None, axis=Axis.C)(x, s) generate_kernel_test_case( description=f"AxiswiseScale for minor axis", backend=["webgpu", "webassembly", "fallback"], - graph=Graph([x], [y]), - inputs={x: vx}, + graph=Graph([x, s], [y]), + inputs={x: vx, s: vs}, expected={y: vy} ) @@ -33,14 +32,14 @@ def test_middle_axis(): vy = vx * vs[None, :, None, None] x = Variable(vx.shape, order=OrderNCHW) - s = ConstantVariable(vs, order=OrderC) + s = Variable(vs.shape, order=OrderC) y, = AxiswiseScale(None, axis=Axis.C)(x, s) generate_kernel_test_case( description=f"AxiswiseScale for middle axis", - backend=["webgpu", "fallback"], - graph=Graph([x], [y]), - inputs={x: vx}, + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x, s], [y]), + inputs={x: vx, s: vs}, expected={y: vy} ) @@ -51,14 +50,14 @@ def test_major_axis(): vy = vx * vs[:, None, None, None] x = Variable(vx.shape, order=OrderCNHW) - s = ConstantVariable(vs, order=OrderC) + s = Variable(vs.shape, order=OrderC) y, = AxiswiseScale(None, axis=Axis.C)(x, s) generate_kernel_test_case( description=f"AxiswiseScale for major axis", - backend=["webgpu", "fallback"], - graph=Graph([x], [y]), - inputs={x: vx}, + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x, s], [y]), + inputs={x: vx, s: vs}, expected={y: vy} ) @@ -69,7 +68,7 @@ def test_mix_order(): vy = vx * vs[:, None, None, None] x = Variable(vx.shape, order=OrderCNHW) - s = ConstantVariable(vs, order=OrderC) + s = Variable(vs.shape, order=OrderC) y, = AxiswiseScale(None, axis=Axis.C)(x, s) x.change_order(OrderNHWC) @@ -77,8 +76,8 @@ def test_mix_order(): generate_kernel_test_case( description=f"AxiswiseScale for mix order", - backend=["webgpu"], - graph=Graph([x], [y]), - inputs={x: vx}, + backend=["webgpu", "webassembly"], + graph=Graph([x, s], [y]), + inputs={x: vx, s: vs}, expected={y: vy} ) diff --git a/test/runtime/operators_test/clipped_relu_test.py b/test/runtime/operators_test/clipped_relu_test.py new file mode 100644 index 000000000..a95e1d793 --- /dev/null +++ b/test/runtime/operators_test/clipped_relu_test.py @@ -0,0 +1,24 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.clipped_relu import ClippedRelu +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +def test_general(): + cap = 0.25 + vx = np.random.rand(2, 3, 4, 5) - 0.5 + vy = np.clip(vx, 0.0, cap) + + x = Variable(vx.shape, order=OrderNHWC) + y, = ClippedRelu(None, cap=cap)(x) + + generate_kernel_test_case( + description=f"ClippedRelu", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/kernels/col2im_test.py b/test/runtime/operators_test/col2im_test.py similarity index 100% rename from test/kernels/col2im_test.py rename to test/runtime/operators_test/col2im_test.py diff --git a/test/kernels/concat_test.py b/test/runtime/operators_test/concat_test.py similarity index 100% rename from test/kernels/concat_test.py rename to test/runtime/operators_test/concat_test.py diff --git a/test/kernels/elementwise_sum_test.py b/test/runtime/operators_test/elementwise_sum_test.py similarity index 100% rename from test/kernels/elementwise_sum_test.py rename to test/runtime/operators_test/elementwise_sum_test.py diff --git a/test/kernels/elu_test.py b/test/runtime/operators_test/elu_test.py similarity index 91% rename from test/kernels/elu_test.py rename to test/runtime/operators_test/elu_test.py index 3b739b76a..205de4284 100644 --- a/test/kernels/elu_test.py +++ b/test/runtime/operators_test/elu_test.py @@ -17,7 +17,7 @@ def test_general(): generate_kernel_test_case( description=f"Elu", - backend=["webgpu", "webassembly"], + backend=["webgpu", "webassembly", "fallback"], graph=Graph([x], [y]), inputs={x: vx}, expected={y: vy} diff --git a/test/runtime/operators_test/embedding_test.py b/test/runtime/operators_test/embedding_test.py new file mode 100644 index 000000000..b6858b1c8 --- /dev/null +++ b/test/runtime/operators_test/embedding_test.py @@ -0,0 +1,26 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.embedding import Embedding +from webdnn.graph.order import OrderNT, OrderCN +from webdnn.graph.variable import Variable +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test_general(): + vx = np.array([[2, 4, 3]]) + vw = np.arange(15).reshape(5, 3) + vy = vw[vx] + + x = Variable(vx.shape, order=OrderNT) + w = ConstantVariable(vw, order=OrderCN) + y, = Embedding(None)(x, w) + + generate_kernel_test_case( + description=f"Embedding", + backend=["webgpu"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/kernels/flatten_test.py b/test/runtime/operators_test/flatten_test.py similarity index 100% rename from test/kernels/flatten_test.py rename to test/runtime/operators_test/flatten_test.py diff --git a/test/runtime/operators_test/hard_sigmoid_test.py b/test/runtime/operators_test/hard_sigmoid_test.py new file mode 100644 index 000000000..c7e72eb2b --- /dev/null +++ b/test/runtime/operators_test/hard_sigmoid_test.py @@ -0,0 +1,23 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.hard_sigmoid import HardSigmoid +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +def test_general(): + vx = np.random.rand(2, 3, 4, 5) - 0.5 + vy = np.clip(vx * 0.2 + 0.5, 0.0, 1.0) + + x = Variable(vx.shape, order=OrderNHWC) + y, = HardSigmoid(None)(x) + + generate_kernel_test_case( + description=f"HardSigmoid", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/kernels/im2col_test.py b/test/runtime/operators_test/im2col_test.py similarity index 58% rename from test/kernels/im2col_test.py rename to test/runtime/operators_test/im2col_test.py index 08a71a093..76712f4ef 100644 --- a/test/kernels/im2col_test.py +++ b/test/runtime/operators_test/im2col_test.py @@ -102,10 +102,10 @@ def test_NHWC(): im = Variable(v_im.shape, order=OrderNHWC) - col_wasm, = WasmIm2Col(None, ksize=3, padding=1, stride=1)(im) + col_wasm, = WasmIm2Col(None, ksize=3, padding=1, stride=1, dilation_rate=1)(im) col_wasm.change_order(OrderNHWC) - col_webgpu, = WebGPUIm2Col(None, ksize=3, padding=1, stride=1)(im) + col_webgpu, = WebGPUIm2Col(None, ksize=3, padding=1, stride=1, dilation_rate=1)(im) col_webgpu.change_order(OrderNHWC) generate_kernel_test_case( @@ -134,10 +134,10 @@ def test_CNHW(): im = Variable(v_im.shape, order=OrderNHWC) - col_wasm, = WasmIm2Col(None, ksize=3, padding=1, stride=1)(im) + col_wasm, = WasmIm2Col(None, ksize=3, padding=1, stride=1, dilation_rate=1)(im) col_wasm.change_order(OrderCNHW) - col_webgpu, = WebGPUIm2Col(None, ksize=3, padding=1, stride=1)(im) + col_webgpu, = WebGPUIm2Col(None, ksize=3, padding=1, stride=1, dilation_rate=1)(im) col_webgpu.change_order(OrderCNHW) generate_kernel_test_case( @@ -163,10 +163,10 @@ def test_wide_stride_NHWC(): im = Variable(v_im.shape, order=OrderNHWC) - col_wasm, = WasmIm2Col(None, ksize=2, padding=1, stride=2)(im) + col_wasm, = WasmIm2Col(None, ksize=2, padding=1, stride=2, dilation_rate=1)(im) col_wasm.change_order(OrderNHWC) - col_webgpu, = WebGPUIm2Col(None, ksize=2, padding=1, stride=2)(im) + col_webgpu, = WebGPUIm2Col(None, ksize=2, padding=1, stride=2, dilation_rate=1)(im) col_webgpu.change_order(OrderNHWC) generate_kernel_test_case( @@ -195,10 +195,10 @@ def test_wide_stride_CNHW(): im = Variable(v_im.shape, order=OrderNHWC) - col_wasm, = WasmIm2Col(None, ksize=2, padding=1, stride=2)(im) + col_wasm, = WasmIm2Col(None, ksize=2, padding=1, stride=2, dilation_rate=1)(im) col_wasm.change_order(OrderCNHW) - col_webgpu, = WebGPUIm2Col(None, ksize=2, padding=1, stride=2)(im) + col_webgpu, = WebGPUIm2Col(None, ksize=2, padding=1, stride=2, dilation_rate=1)(im) col_webgpu.change_order(OrderCNHW) generate_kernel_test_case( @@ -217,3 +217,118 @@ def test_wide_stride_CNHW(): inputs={im: v_im}, expected={col_webgpu: col_dummy.data} ) + + +# Dilated convolution +def generate_data_3112(): + v_im = np.array([[np.arange(1, 26).reshape(5, 5), + np.arange(26, 51).reshape(5, 5)]]).astype(np.float) # Order: NCHW + v_im = np.rollaxis(v_im, 1, 4) # Order: NHWC + + v_col_h_w_kh_kw = np.array( + [[[ + [0, 0, 0], + [0, 7, 9], + [0, 17, 19], + ], [ + [0, 0, 0], + [6, 8, 10], + [16, 18, 20], + ], [ + [0, 0, 0], + [7, 9, 0], + [17, 19, 0] + ]], [[ + [0, 2, 4], + [0, 12, 14], + [0, 22, 24], + ], [ + [1, 3, 5], + [11, 13, 15], + [21, 23, 25], + ], [ + [2, 4, 0], + [12, 14, 0], + [22, 24, 0] + ]], [[ + [0, 7, 9], + [0, 17, 19], + [0, 0, 0] + ], [ + [6, 8, 10], + [16, 18, 20], + [0, 0, 0] + ], [ + [7, 9, 0], + [17, 19, 0], + [0, 0, 0] + ]]], + dtype=np.float32 + ) + v_col_h_w_kh_kw_ch1 = v_col_h_w_kh_kw + 25 + v_col_h_w_kh_kw_ch1[v_col_h_w_kh_kw == 0] = 0 + v_col = np.array([[v_col_h_w_kh_kw, v_col_h_w_kh_kw_ch1]]) # Order: (N, C, H, W, KH, KW) + v_col = np.rollaxis(v_col, 1, 6).reshape(1, 3, 3, 3 * 3 * 2) # Order: NHWC + + return v_im, v_col + + +def test_dilated_NHWC(): + v_im, v_col = generate_data_3112() + + im = Variable(v_im.shape, order=OrderNHWC) + + col_wasm, = WasmIm2Col(None, ksize=3, padding=1, stride=1, dilation_rate=2)(im) + col_wasm.change_order(OrderNHWC) + + col_webgpu, = WebGPUIm2Col(None, ksize=3, padding=1, stride=1, dilation_rate=2)(im) + col_webgpu.change_order(OrderNHWC) + + generate_kernel_test_case( + description=f"Im2Col output=NHWC dilation_rate=2", + backend=["webassembly"], + graph=Graph([im], [col_wasm]), + inputs={im: v_im}, + expected={col_wasm: v_col}, + raise_skip=False + ) + + generate_kernel_test_case( + description=f"Im2Col output=NHWC dilation_rate=2", + backend=["webgpu"], + graph=Graph([im], [col_webgpu]), + inputs={im: v_im}, + expected={col_webgpu: v_col} + ) + + +def test_dilated_CNHW(): + v_im, v_col = generate_data_3112() + + col_dummy = ConstantVariable(v_col, order=OrderNHWC) + col_dummy.change_order(OrderCNHW) + + im = Variable(v_im.shape, order=OrderNHWC) + + col_wasm, = WasmIm2Col(None, ksize=3, padding=1, stride=1, dilation_rate=2)(im) + col_wasm.change_order(OrderCNHW) + + col_webgpu, = WebGPUIm2Col(None, ksize=3, padding=1, stride=1, dilation_rate=2)(im) + col_webgpu.change_order(OrderCNHW) + + generate_kernel_test_case( + description=f"Im2Col output=CNHW dilation_rate=2", + backend=["webassembly"], + graph=Graph([im], [col_wasm]), + inputs={im: v_im}, + expected={col_wasm: col_dummy.data}, + raise_skip=False + ) + + generate_kernel_test_case( + description=f"Im2Col output=CNHW dilation_rate=2", + backend=["webgpu"], + graph=Graph([im], [col_webgpu]), + inputs={im: v_im}, + expected={col_webgpu: col_dummy.data} + ) diff --git a/test/runtime/operators_test/leaky_relu_test.py b/test/runtime/operators_test/leaky_relu_test.py new file mode 100644 index 000000000..183264174 --- /dev/null +++ b/test/runtime/operators_test/leaky_relu_test.py @@ -0,0 +1,24 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.leaky_relu import LeakyRelu +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +def test_general(): + slope = 0.5 + vx = np.random.rand(2, 3, 4, 5) - 0.5 + vy = np.maximum(vx, vx * slope) + + x = Variable(vx.shape, order=OrderNHWC) + y, = LeakyRelu(None, slope=slope)(x) + + generate_kernel_test_case( + description=f"LeakyRelu", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/runtime/operators_test/linear_test.py b/test/runtime/operators_test/linear_test.py new file mode 100644 index 000000000..7ab8d0f2b --- /dev/null +++ b/test/runtime/operators_test/linear_test.py @@ -0,0 +1,46 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.linear import Linear +from webdnn.graph.order import OrderNC, OrderCN, OrderHWCN, OrderNHWC +from webdnn.graph.variable import Variable +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def test_NC_CN(): + vx = np.random.rand(3, 4) + vw = np.random.rand(4, 5) + vy = np.dot(vx, vw) + + x = Variable(vx.shape, order=OrderNC) + w = ConstantVariable(vw, order=OrderCN) + y, = Linear(None)(x, w) + + generate_kernel_test_case( + description=f"Linear: NC*CN", + backend=["fallback", "webassembly", "webgpu"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy}, + raise_skip=False + ) + + +def test_NHWC_HWCN(): + vx = np.random.rand(2, 3, 4, 5) + vw = np.random.rand(3, 4, 5, 2) + vy = np.tensordot(vx, vw, ((1, 2, 3), (0, 1, 2))) + + x = Variable(vx.shape, order=OrderNHWC) + w = ConstantVariable(vw, order=OrderHWCN) + y, = Linear(None)(x, w) + + generate_kernel_test_case( + description=f"Linear: NHWC*HWCN", + backend=["fallback", "webassembly", "webgpu"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy}, + raise_skip=False + ) diff --git a/test/runtime/operators_test/local_response_normalization_test.py b/test/runtime/operators_test/local_response_normalization_test.py new file mode 100644 index 000000000..cb756630d --- /dev/null +++ b/test/runtime/operators_test/local_response_normalization_test.py @@ -0,0 +1,39 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.axis import Axis +from webdnn.graph.graph import Graph +from webdnn.graph.operators.local_response_normalization import LocalResponseNormalization +from webdnn.graph.order import OrderC, OrderNHWC, OrderNCHW, OrderCNHW +from webdnn.graph.variable import Variable + + +def test_major_axis(): + vx = np.random.rand(10, 6, 4, 8) + n = 5 + k = 2 + alpha = 1e-4 + beta = 0.75 + + np_axis = 3 + vx_squared = vx ** 2.0 + vx_scales = [] + for i in range(vx.shape[np_axis]): + # if axis == 2: vx_squared[:, :, i-n//2:i+n//2+1, :] + channel_slice = [slice(None)] * np_axis + \ + [slice(max(0, i - n // 2), min(i + n // 2 + 1, vx.shape[np_axis]))] + \ + [slice(None)] * (vx.ndim - np_axis - 1) + vx_scales.append(np.sum(vx_squared[channel_slice], axis=np_axis, keepdims=True)) + vx_scale = (np.concatenate(vx_scales, axis=np_axis) * alpha + k) ** (-beta) + vy = vx * vx_scale + + x = Variable(vx.shape, order=OrderNHWC) + y, = LocalResponseNormalization(None, n=n, k=k, alpha=alpha, beta=beta)(x) + + generate_kernel_test_case( + description=f"LocalResponseNormalization for major axis", + backend=["webgpu", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/runtime/operators_test/lstm_test.py b/test/runtime/operators_test/lstm_test.py new file mode 100644 index 000000000..b9d2e44cc --- /dev/null +++ b/test/runtime/operators_test/lstm_test.py @@ -0,0 +1,242 @@ +import numpy as np +from chainer import Variable +from chainer.functions.activation.lstm import lstm +from chainer.functions.connection.linear import linear + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.lstm import LSTM +from webdnn.graph.order import OrderNTC, OrderCN, OrderC, OrderNC +from webdnn.graph.variable import Variable +from webdnn.graph.variables.constant_variable import ConstantVariable + + +def _convert_to_chainer_order(x): + # NOTE: + # In WebDNN, W_i, W_h, and b contains weights about input gate(v_i), forget gate(v_f) activated value(v_a), and output gate(v_o) + # based on this order (v_i, v_f, v_a, v_o). However in chainer, they are packed in different order (v_a, v_i, v_f, and v_o). + # Also, webdnn packs this weights as an tensor whose shape is (C1 or C2, 4, C2), but chainer packs as (C1 or C2, C2, 4) + return x.reshape(x.shape[0], 4, x.shape[1] // 4).swapaxes(1, 2)[:, :, [2, 0, 1, 3]].reshape(x.shape) + + +def test_t_is_1(): + np.random.seed(2) + N = 1 + T = 1 + C1 = 128 + C2 = 32 + vx = np.random.normal(size=(N, T, C1)).astype(np.float32) + vw_input = np.random.normal(size=(C1, C2 * 4)).astype(np.float32) + vw_hidden = np.random.normal(size=(C2, C2 * 4)).astype(np.float32) + vb = np.random.normal(size=(C2 * 4,)).astype(np.float32) + vc_in = np.zeros((N, C2)).astype(np.float32) + vc_out = vc_in.copy() + vh = np.zeros((N, C2)).astype(np.float32) + + vw_input_c = _convert_to_chainer_order(vw_input) + vw_hidden_c = _convert_to_chainer_order(vw_hidden) + vb_c = _convert_to_chainer_order(vb[None, :]) + + for i in range(T): + vc_out, vh = lstm(vc_out, linear(vx[:, i, :], vw_input_c.T) + linear(vh, vw_hidden_c.T) + vb_c) + + vh = vh.data + vc_out = vc_out.data + + x = Variable(vx.shape, order=OrderNTC) + # c_in = ConstantVariable(vc_in, order=OrderNC) + w_input = ConstantVariable(vw_input, order=OrderCN) + w_hidden = ConstantVariable(vw_hidden, order=OrderCN) + b = ConstantVariable(vb, order=OrderC) + y, c_out = LSTM(None, return_sequences=False, use_bias=True, use_initial_c=False, use_initial_h=False, + activation="tanh", recurrent_activation="sigmoid")(x, w_input, w_hidden, b) + + generate_kernel_test_case( + description=f"LSTM t=1", + backend=["webassembly", "webgpu"], + graph=Graph([x], [y, c_out]), + inputs={x: vx}, + expected={y: vh, c_out: vc_out}, + EPS=1e-3, + ABS_EPS=1e-7 + ) + + +def test_t_is_5(): + np.random.seed(2) + N = 1 + T = 5 + C1 = 128 + C2 = 32 + vx = np.random.normal(size=(N, T, C1)).astype(np.float32) + vw_input = np.random.normal(size=(C1, C2 * 4)).astype(np.float32) + vw_hidden = np.random.normal(size=(C2, C2 * 4)).astype(np.float32) + vb = np.random.normal(size=(C2 * 4,)).astype(np.float32) + vc_in = np.zeros((N, C2)).astype(np.float32) + vc_out = vc_in.copy() + vh = np.zeros((N, C2)).astype(np.float32) + + vw_input_c = _convert_to_chainer_order(vw_input) + vw_hidden_c = _convert_to_chainer_order(vw_hidden) + vb_c = _convert_to_chainer_order(vb[None, :]) + + for i in range(T): + vc_out, vh = lstm(vc_out, linear(vx[:, i, :], vw_input_c.T) + linear(vh, vw_hidden_c.T) + vb_c) + + vh = vh.data + vc_out = vc_out.data + + x = Variable(vx.shape, order=OrderNTC) + # c_in = ConstantVariable(vc_in, order=OrderNC) + w_input = ConstantVariable(vw_input, order=OrderCN) + w_hidden = ConstantVariable(vw_hidden, order=OrderCN) + b = ConstantVariable(vb, order=OrderC) + y, c_out = LSTM(None, return_sequences=False, use_bias=True, use_initial_c=False, use_initial_h=False, + activation="tanh", recurrent_activation="sigmoid")(x, w_input, w_hidden, b) + + generate_kernel_test_case( + description=f"LSTM t=5", + backend=["webassembly", "webgpu"], + graph=Graph([x], [y, c_out]), + inputs={x: vx}, + expected={y: vh, c_out: vc_out}, + EPS=1e-3, + ABS_EPS=1e-7 + ) + + +def test_t_is_10(): + np.random.seed(2) + N = 1 + T = 10 + C1 = 128 + C2 = 64 + vx = np.random.normal(size=(N, T, C1)).astype(np.float32) + vw_input = np.random.normal(size=(C1, C2 * 4)).astype(np.float32) + vw_hidden = np.random.normal(size=(C2, C2 * 4)).astype(np.float32) + vb = np.random.normal(size=(C2 * 4,)).astype(np.float32) + vc_in = np.zeros((N, C2)).astype(np.float32) + vc_out = vc_in.copy() + vh = np.zeros((N, C2)).astype(np.float32) + + vw_input_c = _convert_to_chainer_order(vw_input) + vw_hidden_c = _convert_to_chainer_order(vw_hidden) + vb_c = _convert_to_chainer_order(vb[None, :]) + + for i in range(T): + vc_out, vh = lstm(vc_out, linear(vx[:, i, :], vw_input_c.T) + linear(vh, vw_hidden_c.T) + vb_c) + + vh = vh.data + vc_out = vc_out.data + + x = Variable(vx.shape, order=OrderNTC) + # c_in = ConstantVariable(vc_in, order=OrderNC) + w_input = ConstantVariable(vw_input, order=OrderCN) + w_hidden = ConstantVariable(vw_hidden, order=OrderCN) + b = ConstantVariable(vb, order=OrderC) + y, c_out = LSTM(None, return_sequences=False, use_bias=True, use_initial_c=False, use_initial_h=False, + activation="tanh", recurrent_activation="sigmoid")(x, w_input, w_hidden, b) + + generate_kernel_test_case( + description=f"LSTM t=10", + backend=["webassembly", "webgpu"], + graph=Graph([x], [y, c_out]), + inputs={x: vx}, + expected={y: vh, c_out: vc_out}, + EPS=1e-3, + ABS_EPS=1e-7 + ) + + +def test_t_is_10_nonzero_c(): + np.random.seed(2) + N = 1 + T = 10 + C1 = 128 + C2 = 64 + vx = np.random.normal(size=(N, T, C1)).astype(np.float32) + vw_input = np.random.normal(size=(C1, C2 * 4)).astype(np.float32) + vw_hidden = np.random.normal(size=(C2, C2 * 4)).astype(np.float32) + vb = np.random.normal(size=(C2 * 4,)).astype(np.float32) + vc_in = np.random.normal(size=(N, C2)).astype(np.float32) + vc_out = vc_in.copy() + vh_in = np.random.normal(size=(N, C2)).astype(np.float32) + vh = vh_in + + vw_input_c = _convert_to_chainer_order(vw_input) + vw_hidden_c = _convert_to_chainer_order(vw_hidden) + vb_c = _convert_to_chainer_order(vb[None, :]) + + for i in range(T): + vc_out, vh = lstm(vc_out, linear(vx[:, i, :], vw_input_c.T) + linear(vh, vw_hidden_c.T) + vb_c) + + vh = vh.data + vc_out = vc_out.data + + x = Variable(vx.shape, order=OrderNTC) + c_in = ConstantVariable(vc_in, order=OrderNC) + vh_in = ConstantVariable(vh_in, order=OrderNC) + w_input = ConstantVariable(vw_input, order=OrderCN) + w_hidden = ConstantVariable(vw_hidden, order=OrderCN) + b = ConstantVariable(vb, order=OrderC) + y, c_out = LSTM(None, return_sequences=False, use_bias=True, use_initial_c=True, use_initial_h=True, + activation="tanh", recurrent_activation="sigmoid")(x, w_input, w_hidden, b, initial_c=c_in, + initial_h=vh_in) + + generate_kernel_test_case( + description=f"LSTM t=10 initial_c,initial_h=nonzero", + backend=["webassembly", "webgpu"], + graph=Graph([x], [y, c_out]), + inputs={x: vx}, + expected={y: vh, c_out: vc_out}, + EPS=1e-3, + ABS_EPS=1e-7 + ) + + +def test_t_is_10_nonzero_c_sequence_output(): + np.random.seed(2) + N = 1 + T = 10 + C1 = 128 + C2 = 64 + vx = np.random.normal(size=(N, T, C1)).astype(np.float32) + vw_input = np.random.normal(size=(C1, C2 * 4)).astype(np.float32) + vw_hidden = np.random.normal(size=(C2, C2 * 4)).astype(np.float32) + vb = np.random.normal(size=(C2 * 4,)).astype(np.float32) + vc_in = np.random.normal(size=(N, C2)).astype(np.float32) + vc_out = vc_in.copy() + vh_in = np.random.normal(size=(N, C2)).astype(np.float32) + vh = vh_in + + vw_input_c = _convert_to_chainer_order(vw_input) + vw_hidden_c = _convert_to_chainer_order(vw_hidden) + vb_c = _convert_to_chainer_order(vb[None, :]) + vh_sequence = [] + + for i in range(T): + vc_out, vh = lstm(vc_out, linear(vx[:, i, :], vw_input_c.T) + linear(vh, vw_hidden_c.T) + vb_c) + vh_sequence.append(vh.data) + + vh = np.array(vh_sequence).transpose((1, 0, 2)) # TNC -> NTC + vc_out = vc_out.data + + x = Variable(vx.shape, order=OrderNTC) + c_in = ConstantVariable(vc_in, order=OrderNC) + vh_in = ConstantVariable(vh_in, order=OrderNC) + w_input = ConstantVariable(vw_input, order=OrderCN) + w_hidden = ConstantVariable(vw_hidden, order=OrderCN) + b = ConstantVariable(vb, order=OrderC) + y, c_out = LSTM(None, return_sequences=True, use_bias=True, use_initial_c=True, use_initial_h=True, + activation="tanh", recurrent_activation="sigmoid")(x, w_input, w_hidden, b, initial_c=c_in, + initial_h=vh_in) + + generate_kernel_test_case( + description=f"LSTM t=10 initial_c,initial_h=nonzero sequence_out", + backend=["webassembly", "webgpu"], + graph=Graph([x], [y, c_out]), + inputs={x: vx}, + expected={y: vh, c_out: vc_out}, + EPS=1e-3, + ABS_EPS=1e-7 + ) diff --git a/test/runtime/operators_test/max_pooling_2d_test.py b/test/runtime/operators_test/max_pooling_2d_test.py new file mode 100644 index 000000000..1f1bcd4bb --- /dev/null +++ b/test/runtime/operators_test/max_pooling_2d_test.py @@ -0,0 +1,73 @@ +import itertools + +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.max_pooling_2d import MaxPooling2D +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +def test_general(): + vx = np.random.rand(2, 4, 6, 8) + vy = np.empty((2, 2, 3, 8)) + KH, KW = (2, 2) + SH, SW = (2, 2) + PH, PW = (0, 0) + + for n, h2, w2, c in itertools.product(range(vy.shape[0]), + range(vy.shape[1]), + range(vy.shape[2]), + range(vy.shape[3])): + v = -float("inf") + for (kh, kw) in itertools.product(range(KH), range(KW)): + h1 = (h2 * SH - PH) + kh + w1 = (w2 * SW - PW) + kw + + v = max(v, 0 if (h1 < 0 or h1 >= 4 or w1 < 0 or w1 >= 6) else vx[n, h1, w1, c]) + + vy[n, h2, w2, c] = v + + x = Variable(vx.shape, order=OrderNHWC) + y, = MaxPooling2D(None, ksize=(KH, KW), stride=(SH, SW), padding=(PH, PW))(x) + + generate_kernel_test_case( + description=f"Average Pooling", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) + + +def test_irregular_size(): + vx = np.random.rand(2, 4, 6, 8) + vy = np.empty((2, 4, 5, 8)) + KH, KW = (3, 4) + SH, SW = (1, 2) + PH, PW = (1, 3) + + for n, h2, w2, c in itertools.product(range(vy.shape[0]), + range(vy.shape[1]), + range(vy.shape[2]), + range(vy.shape[3])): + v = -float("inf") + for (kh, kw) in itertools.product(range(KH), range(KW)): + h1 = (h2 * SH - PH) + kh + w1 = (w2 * SW - PW) + kw + + v = max(v, 0 if (h1 < 0 or h1 >= 4 or w1 < 0 or w1 >= 6) else vx[n, h1, w1, c]) + + vy[n, h2, w2, c] = v + + x = Variable(vx.shape, order=OrderNHWC) + y, = MaxPooling2D(None, ksize=(KH, KW), stride=(SH, SW), padding=(PH, PW))(x) + + generate_kernel_test_case( + description=f"Average Pooling with irregular window size", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/kernels/relu_test.py b/test/runtime/operators_test/relu_test.py similarity index 100% rename from test/kernels/relu_test.py rename to test/runtime/operators_test/relu_test.py diff --git a/test/kernels/scalar_affine_test.py b/test/runtime/operators_test/scalar_affine_test.py similarity index 100% rename from test/kernels/scalar_affine_test.py rename to test/runtime/operators_test/scalar_affine_test.py diff --git a/test/kernels/sgemm_test.py b/test/runtime/operators_test/sgemm_test.py similarity index 100% rename from test/kernels/sgemm_test.py rename to test/runtime/operators_test/sgemm_test.py diff --git a/test/runtime/operators_test/sigmoid_test.py b/test/runtime/operators_test/sigmoid_test.py new file mode 100644 index 000000000..69f9d1be7 --- /dev/null +++ b/test/runtime/operators_test/sigmoid_test.py @@ -0,0 +1,23 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.sigmoid import Sigmoid +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +def test_general(): + vx = np.random.rand(2, 3, 4, 5) - 0.5 + vy = 1 / (1 + np.exp(-vx)) + + x = Variable(vx.shape, order=OrderNHWC) + y, = Sigmoid(None)(x) + + generate_kernel_test_case( + description=f"Sigmoid", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/runtime/operators_test/softmax_test.py b/test/runtime/operators_test/softmax_test.py new file mode 100644 index 000000000..94bba46b1 --- /dev/null +++ b/test/runtime/operators_test/softmax_test.py @@ -0,0 +1,24 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.axis import Axis +from webdnn.graph.graph import Graph +from webdnn.graph.operators.softmax import Softmax +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +def test_general(): + vx = np.random.rand(2, 3, 4, 5) - 0.5 + vy = np.exp(vx) / np.sum(np.exp(vx), axis=3, keepdims=True) + + x = Variable(vx.shape, order=OrderNHWC) + y, = Softmax(None, axis=Axis.C)(x) + + generate_kernel_test_case( + description=f"Softmax", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/runtime/operators_test/softplus_test.py b/test/runtime/operators_test/softplus_test.py new file mode 100644 index 000000000..b6c2a7e71 --- /dev/null +++ b/test/runtime/operators_test/softplus_test.py @@ -0,0 +1,24 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.softplus import Softplus +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +def test_general(): + beta = 2.0 + vx = np.random.rand(2, 3, 4, 5) - 0.5 + vy = np.log(np.exp(vx * beta) + 1.0) / beta + + x = Variable(vx.shape, order=OrderNHWC) + y, = Softplus(None, beta=beta)(x) + + generate_kernel_test_case( + description=f"Softplus", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/runtime/operators_test/softsign_test.py b/test/runtime/operators_test/softsign_test.py new file mode 100644 index 000000000..50245892b --- /dev/null +++ b/test/runtime/operators_test/softsign_test.py @@ -0,0 +1,23 @@ +import numpy as np + +from test.util import generate_kernel_test_case +from webdnn.graph.graph import Graph +from webdnn.graph.operators.softsign import Softsign +from webdnn.graph.order import OrderNHWC +from webdnn.graph.variable import Variable + + +def test_general(): + vx = np.random.rand(2, 3, 4, 5) - 0.5 + vy = vx / (np.abs(vx) + 1.0) + + x = Variable(vx.shape, order=OrderNHWC) + y, = Softsign(None)(x) + + generate_kernel_test_case( + description=f"Softsign", + backend=["webgpu", "webassembly", "fallback"], + graph=Graph([x], [y]), + inputs={x: vx}, + expected={y: vy} + ) diff --git a/test/kernels/tanh_test.py b/test/runtime/operators_test/tanh_test.py similarity index 90% rename from test/kernels/tanh_test.py rename to test/runtime/operators_test/tanh_test.py index 247e53ce6..80247cc22 100644 --- a/test/kernels/tanh_test.py +++ b/test/runtime/operators_test/tanh_test.py @@ -16,7 +16,7 @@ def test_general(): generate_kernel_test_case( description=f"Tanh", - backend=["webgpu", "webassembly"], + backend=["webgpu", "webassembly", "fallback"], graph=Graph([x], [y]), inputs={x: vx}, expected={y: vy} diff --git a/test/test_kernel.html b/test/test_kernel.html new file mode 100644 index 000000000..2f2be617f --- /dev/null +++ b/test/test_kernel.html @@ -0,0 +1,29 @@ + + + + + + + Kernel Test + + + +

Kernel Test

+

+ Click RUN button and check console logs. +

+ +
+ + +
+ + + + + + diff --git a/test/test_kernel.js b/test/test_kernel.js new file mode 100644 index 000000000..d991b3867 --- /dev/null +++ b/test/test_kernel.js @@ -0,0 +1,119 @@ +/// +const assert = new class { + constructor() { + this.EPS = 1e-4; + this.ABS_EPS = 0.0; + } + equal(expected, real, description) { + if (expected !== real) + throw Error(`${description ? description + ': ' : ''}(expected: ${expected}) != (real: ${real})`); + } + floatEqual(expected, real, description) { + if (!(Math.abs(expected - real) <= (this.ABS_EPS + this.EPS * Math.abs(expected)))) { + throw Error(`${description ? description + ': ' : ''}(expected: ${expected}) != (real: ${real})`); + } + } + floatArrayEqual(expected, real, description) { + for (let i = 0; i < expected.length; i++) { + try { + this.floatEqual(expected[i], real[i]); + } + catch (e) { + throw Error(e.message + .replace('expected', `expected[${i}]`) + .replace('real', `real[${i}]`)); + } + } + } +}; +//noinspection JSUnusedGlobalSymbols +const TestRunner = new class { + constructor() { + this.testCases = []; + this.results = []; + this.currentTestCaseIndex = 0; + } + async setup() { + let masterJSONUrl = document.getElementById('masterJSONUrl').value; + let res = await fetch(masterJSONUrl); + this.testCases = await res.json(); + this.rootUrl = masterJSONUrl.split('/').slice(0, masterJSONUrl.split('/').length - 1).join('/') + '/'; + this.results = []; + this.currentTestCaseIndex = 0; + console.group('Setup'); + console.log('- TestRunner loaded test case(s)'); + console.log('- # of test case(s): ' + this.testCases.length); + console.groupEnd(); + } + cleanUp() { + let results = this.results; + console.group('Result'); + let failedResults = results.filter(result => !result.result); + if (failedResults.length == 0) { + console.info(`- ${results.length} PASSED / 0 FAILED`); + } + else { + console.error(`- ${results.length - failedResults.length} PASSED / ${failedResults.length} FAILED`); + } + failedResults.forEach(result => { + console.group(result.name); + console.log('- ' + result.err.message); + console.groupEnd(); + }); + console.groupEnd(); + } + async mainLoop() { + await this.runTestCase(); + this.currentTestCaseIndex++; + if (this.currentTestCaseIndex < this.testCases.length) + return this.mainLoop(); + } + async runTestCase() { + const testCase = this.testCases[this.currentTestCaseIndex]; + const testName = `[${testCase.backend}] ${testCase.description}`; + let elapsedTime; + console.group(`[${this.currentTestCaseIndex + 1}/${this.testCases.length}]${testName}`); + try { + assert.EPS = testCase.EPS; + assert.ABS_EPS = testCase.ABS_EPS; + let runner = await WebDNN.load(this.rootUrl + testCase.dirname, { + backendOrder: testCase.backend, + ignoreCache: true + }); + assert.equal(testCase.backend, runner.backendName, 'backend'); + let inputs = runner.getInputViews(); + let outputs = runner.getOutputViews(); + testCase.inputs.forEach((data, i) => inputs[i].set(data)); + let startTime = performance.now(); + await runner.run(); + elapsedTime = performance.now() - startTime; + testCase.expected.forEach((expected, i) => assert.floatArrayEqual(expected, outputs[i].toActual(), `outputs[${i}]`)); + this.results.push({ + name: testName, + testCase: testCase, + result: true, + elapsedTime: elapsedTime, + }); + console.log('- PASS: Elapsed time=' + (elapsedTime).toFixed(2) + '[ms]'); + } + catch (err) { + this.results.push({ + name: testName, + testCase: testCase, + result: false, + elapsedTime: -1, + err: err + }); + console.error(err); + } + console.groupEnd(); + } + async run() { + return this.setup() + .then(() => console.group('Run')) + .then(() => this.mainLoop()) + .then(() => console.groupEnd()) + .then(() => this.cleanUp()) + .catch(err => console.error(err)); + } +}; diff --git a/test/test_kernel.ts b/test/test_kernel.ts new file mode 100644 index 000000000..40c588fce --- /dev/null +++ b/test/test_kernel.ts @@ -0,0 +1,156 @@ +/// + +const assert = new class { + EPS = 1e-4; + ABS_EPS = 0.0; + + equal(expected: T, real: T, description?: string) { + if (expected !== real) throw Error(`${description ? description + ': ' : ''}(expected: ${expected}) != (real: ${real})`); + } + + floatEqual(expected: number, real: number, description?: string) { + if (!(Math.abs(expected - real) <= (this.ABS_EPS + this.EPS * Math.abs(expected)))) { + throw Error(`${description ? description + ': ' : ''}(expected: ${expected}) != (real: ${real})`); + } + } + + floatArrayEqual(expected: ArrayLike, real: ArrayLike, description?: string) { + for (let i = 0; i < expected.length; i++) { + try { + this.floatEqual(expected[i], real[i]); + } catch (e) { + throw Error(e.message + .replace('expected', `expected[${i}]`) + .replace('real', `real[${i}]`)); + } + } + } +}; + +interface TestCase { + description: string, + backend: string, + dirname: string, + inputs: number[][], + expected: number[][], + EPS: number, + ABS_EPS: number +} + +interface Result { + name: string, + testCase: TestCase, + err?: Error, + result: boolean, + elapsedTime: number +} + +//noinspection JSUnusedGlobalSymbols +const TestRunner = new class { + testCases: TestCase[] = []; + rootUrl: string; + results: Result[] = []; + currentTestCaseIndex = 0; + + async setup() { + let masterJSONUrl = (document.getElementById('masterJSONUrl')! as HTMLInputElement).value; + + let res = await fetch(masterJSONUrl); + this.testCases = await res.json(); + this.rootUrl = masterJSONUrl.split('/').slice(0, masterJSONUrl.split('/').length - 1).join('/') + '/'; + this.results = []; + this.currentTestCaseIndex = 0; + + console.group('Setup'); + console.log('- TestRunner loaded test case(s)'); + console.log('- # of test case(s): ' + this.testCases.length); + console.groupEnd(); + } + + cleanUp() { + let results = this.results; + console.group('Result'); + + let failedResults = results.filter(result => !result.result); + if (failedResults.length == 0) { + console.info(`- ${results.length} PASSED / 0 FAILED`); + } else { + console.error(`- ${results.length - failedResults.length} PASSED / ${failedResults.length} FAILED`); + } + + failedResults.forEach(result => { + console.group(result.name); + console.log('- ' + result.err.message); + console.groupEnd(); + }); + + console.groupEnd(); + } + + async mainLoop() { + await this.runTestCase(); + + this.currentTestCaseIndex++; + if (this.currentTestCaseIndex < this.testCases.length) return this.mainLoop(); + } + + async runTestCase() { + const testCase = this.testCases[this.currentTestCaseIndex]; + const testName = `[${testCase.backend}] ${testCase.description}`; + let elapsedTime: number; + + console.group(`[${this.currentTestCaseIndex + 1}/${this.testCases.length}]${testName}`); + + try { + assert.EPS = testCase.EPS; + assert.ABS_EPS = testCase.ABS_EPS; + + let runner = await WebDNN.load(this.rootUrl + testCase.dirname, { + backendOrder: testCase.backend, + ignoreCache: true + }); + assert.equal(testCase.backend, runner.backendName, 'backend'); + + let inputs = runner.getInputViews(); + let outputs = runner.getOutputViews(); + + testCase.inputs.forEach((data, i) => inputs[i].set(data)); + let startTime = performance.now(); + await runner.run(); + elapsedTime = performance.now() - startTime; + + testCase.expected.forEach((expected, i) => assert.floatArrayEqual(expected, outputs[i].toActual(), `outputs[${i}]`)); + + this.results.push({ + name: testName, + testCase: testCase, + result: true, + elapsedTime: elapsedTime, + }); + + console.log('- PASS: Elapsed time=' + (elapsedTime).toFixed(2) + '[ms]'); + + } catch (err) { + this.results.push({ + name: testName, + testCase: testCase, + result: false, + elapsedTime: -1, + err: err + }); + + console.error(err); + } + + console.groupEnd(); + } + + async run() { + return this.setup() + .then(() => console.group('Run')) + .then(() => this.mainLoop()) + .then(() => console.groupEnd()) + .then(() => this.cleanUp()) + .catch(err => console.error(err)) + } +}; diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 000000000..1b8136a37 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es2017", + "strictNullChecks": false, + "outFile": "test_kernel.js", + "lib": [ + "es2017", + "dom" + ], + "declaration": false, + "sourceMap": false + }, + "files": [ + "test_kernel.ts" + ] +} diff --git a/test/util.py b/test/util.py index f2e17f4c8..2f5ff16ea 100644 --- a/test/util.py +++ b/test/util.py @@ -2,9 +2,10 @@ import os.path as path import shutil from abc import abstractmethod -from typing import Type, Dict, List, Union, Iterable +from typing import Type, Dict, List, Optional, Union from unittest import SkipTest +import chainer import numpy as np from webdnn.backend.interface.generator import generate_descriptor @@ -16,11 +17,11 @@ from webdnn.util.json import json -def template_elementwise_operator(OperatorClass: Type[Operator]): +def template_elementwise_operator(OperatorClass: Type[Operator], operator_kwargs: Optional[Dict[str, any]] = None): orders = [OrderC, OrderNC, OrderCN, OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] for order in orders: - op = OperatorClass("op") + op = OperatorClass("op", **(operator_kwargs or {})) x = Variable(np.arange(order.ndim) + 1, order) y, = op(x) @@ -66,22 +67,35 @@ def setup(cls): @classmethod def generate_kernel_test_case(cls, description: str, - backend: Union[str, Iterable[str]], graph: Graph, inputs: Dict[Variable, np.array], expected: Dict[Variable, np.array], - raise_skip: bool = True): + backend=None, + raise_skip: bool = True, + EPS: float = 1.0e-3, + ABS_EPS: float = 0.0): """Generate test data for generated kernel codes Generated data are saved in JSON format, and BrowserTestRunner executes it. """ + if backend is None: + backend = ["webgpu", "webassembly", "fallback"] + if not cls.flag_initialized: cls.setup() if not isinstance(backend, str): for b in backend: - generate_kernel_test_case(description, b, graph, inputs, expected, False) + generate_kernel_test_case( + description=description, + graph=graph, + inputs=inputs, + expected=expected, + backend=b, + raise_skip=False, + EPS=EPS, + ABS_EPS=ABS_EPS) if raise_skip: raise SkipTest(f"[BrowserTest|{backend}] {description}") @@ -100,7 +114,9 @@ def generate_kernel_test_case(cls, "inputs": [list(inputs[v].flatten()) for v in graph.inputs], "expected": [list(expected[v].flatten()) for v in graph.outputs], "dirname": testcase_dirname, - "backend": backend + "backend": backend, + "EPS": EPS, + "ABS_EPS": ABS_EPS }) if raise_skip: diff --git a/test/webdnn_test/graph_test/backend_test/webgpu_test/operators_test/col2im_test.py b/test/webdnn_test/backend_test/webgpu_test/operators_test/col2im_test.py similarity index 96% rename from test/webdnn_test/graph_test/backend_test/webgpu_test/operators_test/col2im_test.py rename to test/webdnn_test/backend_test/webgpu_test/operators_test/col2im_test.py index 1e686d942..8f2eb84a3 100644 --- a/test/webdnn_test/graph_test/backend_test/webgpu_test/operators_test/col2im_test.py +++ b/test/webdnn_test/backend_test/webgpu_test/operators_test/col2im_test.py @@ -9,7 +9,6 @@ orders4 = [OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] -# FIXME 各orderをテストにわけられないか def main(k, s, p, n, h1, w1, c1, expected_shape_dict: Dict[Axis, int]): for order_x in orders4: op = Col2Im(None, ksize=k, stride=s, padding=p) diff --git a/test/webdnn_test/graph_test/backend_test/webgpu_test/operators_test/im2col_test.py b/test/webdnn_test/backend_test/webgpu_test/operators_test/im2col_test.py similarity index 70% rename from test/webdnn_test/graph_test/backend_test/webgpu_test/operators_test/im2col_test.py rename to test/webdnn_test/backend_test/webgpu_test/operators_test/im2col_test.py index 74651157f..6e1bbac37 100644 --- a/test/webdnn_test/graph_test/backend_test/webgpu_test/operators_test/im2col_test.py +++ b/test/webdnn_test/backend_test/webgpu_test/operators_test/im2col_test.py @@ -9,10 +9,9 @@ orders4 = [OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] -# FIXME 各orderをテストにわけられないか -def main(k, s, p, n, h1, w1, c1, expected_shape_dict: Dict[Axis, int]): +def main(k, s, p, d, n, h1, w1, c1, expected_shape_dict: Dict[Axis, int]): for order_x in orders4: - op = Im2Col("im2col", ksize=k, stride=s, padding=p) + op = Im2Col("im2col", ksize=k, stride=s, padding=p, dilation_rate=d) x = Variable((n, h1, w1, c1), OrderNHWC) x.change_order(order_x) @@ -24,7 +23,7 @@ def main(k, s, p, n, h1, w1, c1, expected_shape_dict: Dict[Axis, int]): def test_normal(): - main(3, 1, 1, 2, 3, 4, 5, { + main(3, 1, 1, 1, 2, 3, 4, 5, { Axis.N: 2, Axis.H: 3, Axis.W: 4, @@ -33,7 +32,7 @@ def test_normal(): def test_large_stride(): - main(3, 2, 1, 2, 5, 7, 3, { + main(3, 2, 1, 1, 2, 5, 7, 3, { Axis.N: 2, Axis.H: 3, Axis.W: 4, @@ -42,7 +41,7 @@ def test_large_stride(): def test_no_padding(): - main(3, 1, 0, 2, 5, 7, 3, { + main(3, 1, 0, 1, 2, 5, 7, 3, { Axis.N: 2, Axis.H: 3, Axis.W: 5, @@ -51,7 +50,7 @@ def test_no_padding(): def test_projection(): - main(1, 1, 0, 2, 5, 7, 3, { + main(1, 1, 0, 1, 2, 5, 7, 3, { Axis.N: 2, Axis.H: 5, Axis.W: 7, @@ -60,9 +59,18 @@ def test_projection(): def test_fully_connected(): - main((5, 7), 1, 0, 2, 5, 7, 3, { + main((5, 7), 1, 0, 1, 2, 5, 7, 3, { Axis.N: 2, Axis.H: 1, Axis.W: 1, Axis.C: 105, }) + + +def test_dilated(): + main(3, 1, 1, 2, 2, 8, 9, 5, { + Axis.N: 2, + Axis.H: 6, + Axis.W: 7, + Axis.C: 45, + }) diff --git a/test/webdnn_test/graph_test/backend_test/webgpu_test/operators_test/sgemm_test.py b/test/webdnn_test/backend_test/webgpu_test/operators_test/sgemm_test.py similarity index 100% rename from test/webdnn_test/graph_test/backend_test/webgpu_test/operators_test/sgemm_test.py rename to test/webdnn_test/backend_test/webgpu_test/operators_test/sgemm_test.py diff --git a/test/webdnn_test/graph_test/backend_test/webgpu_test/optimize_rule_test/sub_rules_test/concat_sgemm_bias_test.py b/test/webdnn_test/backend_test/webgpu_test/optimize_rule_test/sub_rules_test/concat_sgemm_bias_test.py similarity index 100% rename from test/webdnn_test/graph_test/backend_test/webgpu_test/optimize_rule_test/sub_rules_test/concat_sgemm_bias_test.py rename to test/webdnn_test/backend_test/webgpu_test/optimize_rule_test/sub_rules_test/concat_sgemm_bias_test.py diff --git a/test/webdnn_test/graph_test/operator_test.py b/test/webdnn_test/graph_test/operator_test.py index 020bb4a6e..a44f2bcbe 100644 --- a/test/webdnn_test/graph_test/operator_test.py +++ b/test/webdnn_test/graph_test/operator_test.py @@ -3,30 +3,6 @@ from webdnn.graph.variable import Variable -def test_get_input_name(): - op = Operator("op") - v1 = Variable((1, 2, 3, 4), OrderNHWC) - v2 = Variable((1, 2, 3, 4), OrderNHWC) - - op.append_input("v1", v1) - op.append_input("v2", v2) - - assert op.get_input_name(v1) == "v1" - assert op.get_input_name(v2) == "v2" - - -def test_get_output_name(): - op = Operator("op") - v1 = Variable((1, 2, 3, 4), OrderNHWC) - v2 = Variable((1, 2, 3, 4), OrderNHWC) - - op.append_output("v1", v1) - op.append_output("v2", v2) - - assert op.get_output_name(v1) == "v1" - assert op.get_output_name(v2) == "v2" - - def test_append_input(): op = Operator("op") v1 = Variable((1, 2, 3, 4), OrderNHWC) diff --git a/test/webdnn_test/graph_test/operators_test/average_pooling_2d_test.py b/test/webdnn_test/graph_test/operators_test/average_pooling_2d_test.py index dbeaa36f3..8adfec278 100644 --- a/test/webdnn_test/graph_test/operators_test/average_pooling_2d_test.py +++ b/test/webdnn_test/graph_test/operators_test/average_pooling_2d_test.py @@ -6,7 +6,6 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def main(k, s, p, n, h1, w1, c1, expected_shape_dict: Dict[Axis, int]): orders_x = [OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] diff --git a/test/webdnn_test/graph_test/operators_test/axiswise_bias_test.py b/test/webdnn_test/graph_test/operators_test/axiswise_bias_test.py index 083c12460..2fdabe12a 100644 --- a/test/webdnn_test/graph_test/operators_test/axiswise_bias_test.py +++ b/test/webdnn_test/graph_test/operators_test/axiswise_bias_test.py @@ -10,7 +10,6 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def test_every_order(): orders_x = [OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] axes = [Axis.C] diff --git a/test/webdnn_test/graph_test/operators_test/axiswise_scale_test.py b/test/webdnn_test/graph_test/operators_test/axiswise_scale_test.py index 32912afca..a4a734876 100644 --- a/test/webdnn_test/graph_test/operators_test/axiswise_scale_test.py +++ b/test/webdnn_test/graph_test/operators_test/axiswise_scale_test.py @@ -10,7 +10,6 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def test_every_order(): orders_x = [OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] axes = [Axis.C] diff --git a/test/webdnn_test/graph_test/operators_test/clipped_relu_test.py b/test/webdnn_test/graph_test/operators_test/clipped_relu_test.py new file mode 100644 index 000000000..387cba1f8 --- /dev/null +++ b/test/webdnn_test/graph_test/operators_test/clipped_relu_test.py @@ -0,0 +1,6 @@ +from test.util import template_elementwise_operator +from webdnn.graph.operators.clipped_relu import ClippedRelu + + +def test_every_order(): + template_elementwise_operator(ClippedRelu, {"cap": 10.0}) diff --git a/test/webdnn_test/graph_test/operators_test/concat_test.py b/test/webdnn_test/graph_test/operators_test/concat_test.py index 0d309686b..9b4c25dc0 100644 --- a/test/webdnn_test/graph_test/operators_test/concat_test.py +++ b/test/webdnn_test/graph_test/operators_test/concat_test.py @@ -34,7 +34,6 @@ def main(order1: Type[Order], order2: Type[Order], concat_axis: Axis): assert y.shape_dict[axis] == x1.shape_dict[axis] -# FIXME 各orderをテストにわけられないか def test_every_order(): orders = [OrderC, OrderNC, OrderCN, OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] axes = [Axis.N, Axis.H, Axis.W, Axis.C] diff --git a/test/webdnn_test/graph_test/operators_test/convolution2d_test.py b/test/webdnn_test/graph_test/operators_test/convolution2d_test.py index de2553b4b..32ebecd1c 100644 --- a/test/webdnn_test/graph_test/operators_test/convolution2d_test.py +++ b/test/webdnn_test/graph_test/operators_test/convolution2d_test.py @@ -7,7 +7,6 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def main(k, s, p, n, h1, w1, c1, c2, expected_shape_dict: Dict[Axis, int]): orders = [OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] diff --git a/test/webdnn_test/graph_test/operators_test/deconvolution2d_test.py b/test/webdnn_test/graph_test/operators_test/deconvolution2d_test.py index 014dc3d27..9177917b3 100644 --- a/test/webdnn_test/graph_test/operators_test/deconvolution2d_test.py +++ b/test/webdnn_test/graph_test/operators_test/deconvolution2d_test.py @@ -7,7 +7,6 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def main(k, s, p, n, h1, w1, c1, c2, expected_shape_dict: Dict[Axis, int]): orders = [OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] diff --git a/test/webdnn_test/graph_test/operators_test/elementwise_sum_test.py b/test/webdnn_test/graph_test/operators_test/elementwise_sum_test.py index 6ae806017..00de16056 100644 --- a/test/webdnn_test/graph_test/operators_test/elementwise_sum_test.py +++ b/test/webdnn_test/graph_test/operators_test/elementwise_sum_test.py @@ -9,7 +9,6 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def test_every_order(): orders = [OrderC, OrderNC, OrderCN, OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] diff --git a/test/webdnn_test/graph_test/operators_test/hard_sigmoid_test.py b/test/webdnn_test/graph_test/operators_test/hard_sigmoid_test.py new file mode 100644 index 000000000..36adae3f4 --- /dev/null +++ b/test/webdnn_test/graph_test/operators_test/hard_sigmoid_test.py @@ -0,0 +1,6 @@ +from test.util import template_elementwise_operator +from webdnn.graph.operators.hard_sigmoid import HardSigmoid + + +def test_every_order(): + template_elementwise_operator(HardSigmoid) diff --git a/test/webdnn_test/graph_test/operators_test/leaky_relu_test.py b/test/webdnn_test/graph_test/operators_test/leaky_relu_test.py new file mode 100644 index 000000000..9eb3b0ea2 --- /dev/null +++ b/test/webdnn_test/graph_test/operators_test/leaky_relu_test.py @@ -0,0 +1,6 @@ +from test.util import template_elementwise_operator +from webdnn.graph.operators.leaky_relu import LeakyRelu + + +def test_every_order(): + template_elementwise_operator(LeakyRelu, {"slope": 0.5}) diff --git a/test/webdnn_test/graph_test/operators_test/linear_test.py b/test/webdnn_test/graph_test/operators_test/linear_test.py index a05f01285..60d91797f 100644 --- a/test/webdnn_test/graph_test/operators_test/linear_test.py +++ b/test/webdnn_test/graph_test/operators_test/linear_test.py @@ -9,7 +9,6 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def test_every_order(): orders = [OrderNC, OrderCN, OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] diff --git a/test/webdnn_test/graph_test/operators_test/local_response_normalization_test.py b/test/webdnn_test/graph_test/operators_test/local_response_normalization_test.py index 438760752..d3cd9d074 100644 --- a/test/webdnn_test/graph_test/operators_test/local_response_normalization_test.py +++ b/test/webdnn_test/graph_test/operators_test/local_response_normalization_test.py @@ -6,7 +6,6 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def test_every_order(): orders = [OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] diff --git a/test/webdnn_test/graph_test/operators_test/max_pooling_2d_test.py b/test/webdnn_test/graph_test/operators_test/max_pooling_2d_test.py index b532366ea..27ca7f149 100644 --- a/test/webdnn_test/graph_test/operators_test/max_pooling_2d_test.py +++ b/test/webdnn_test/graph_test/operators_test/max_pooling_2d_test.py @@ -6,7 +6,6 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def main(k, s, p, n, h1, w1, c1, expected_shape_dict: Dict[Axis, int]): orders = [OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] diff --git a/test/webdnn_test/graph_test/operators_test/scalar_affine.py b/test/webdnn_test/graph_test/operators_test/scalar_affine.py index 8d009136e..cf86023ea 100644 --- a/test/webdnn_test/graph_test/operators_test/scalar_affine.py +++ b/test/webdnn_test/graph_test/operators_test/scalar_affine.py @@ -1,19 +1,6 @@ -import numpy as np - +from test.util import template_elementwise_operator from webdnn.graph.operators.scalar_affine import ScalarAffine -from webdnn.graph.order import OrderHWNC, OrderNHWC, OrderCN, OrderNC, OrderC, OrderCHWN, OrderCNHW, OrderNCHW, \ - OrderHWCN -from webdnn.graph.variable import Variable -# FIXME: use template_elementwise_operator def test_every_order(): - orders = [OrderC, OrderNC, OrderCN, OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] - - for order in orders: - op = ScalarAffine(None, scale=1, bias=0) - - x = Variable(np.arange(order.ndim) + 1, order) - y, = op(x) - for axis in y.order.axes: - assert y.shape_dict[axis] == x.shape_dict[axis] + template_elementwise_operator(ScalarAffine, operator_kwargs={"scale": 1, "bias": 0}) diff --git a/test/webdnn_test/graph_test/operators_test/softmax_test.py b/test/webdnn_test/graph_test/operators_test/softmax_test.py index 9c1b00711..b54edc921 100644 --- a/test/webdnn_test/graph_test/operators_test/softmax_test.py +++ b/test/webdnn_test/graph_test/operators_test/softmax_test.py @@ -6,12 +6,11 @@ from webdnn.graph.variable import Variable -# FIXME 各orderをテストにわけられないか def test_every_order(): orders = [OrderC, OrderNC, OrderCN, OrderNHWC, OrderHWNC, OrderHWCN, OrderNCHW, OrderCNHW, OrderCHWN] for order in orders: - op = Softmax("op") + op = Softmax("op", axis=order.axes[-1]) x = Variable(np.arange(order.ndim) + 1, order) y, = op(x) diff --git a/test/webdnn_test/graph_test/operators_test/softplus_test.py b/test/webdnn_test/graph_test/operators_test/softplus_test.py new file mode 100644 index 000000000..fae892455 --- /dev/null +++ b/test/webdnn_test/graph_test/operators_test/softplus_test.py @@ -0,0 +1,6 @@ +from test.util import template_elementwise_operator +from webdnn.graph.operators.softplus import Softplus + + +def test_every_order(): + template_elementwise_operator(Softplus, {"beta": 1.0}) diff --git a/test/webdnn_test/graph_test/operators_test/softsign_test.py b/test/webdnn_test/graph_test/operators_test/softsign_test.py new file mode 100644 index 000000000..840d18230 --- /dev/null +++ b/test/webdnn_test/graph_test/operators_test/softsign_test.py @@ -0,0 +1,6 @@ +from test.util import template_elementwise_operator +from webdnn.graph.operators.softsign import Softsign + + +def test_every_order(): + template_elementwise_operator(Softsign) diff --git a/test/webdnn_test/graph_test/placeholder_test.py b/test/webdnn_test/graph_test/placeholder_test.py new file mode 100644 index 000000000..973811bd8 --- /dev/null +++ b/test/webdnn_test/graph_test/placeholder_test.py @@ -0,0 +1,203 @@ +from webdnn.graph.placeholder import Placeholder + + +def test_resolved1(): + assert Placeholder(value=1) == 1 + + +def test_resolved2(): + p = Placeholder() + p.value = 1 + assert Placeholder(value=1) == p + + +def test_add1(): + p = Placeholder() + q = p + 1 + p.value = 2 + assert q == 3 + + +def test_add2(): + p = Placeholder() + q = p + 1 + r = q + 2 + p.value = 2 + assert r == 5 + + +def test_add3(): + p = Placeholder() + q = p + 1 + r = 2 + q + p.value = 2 + assert r == 5 + + +def test_radd1(): + p = Placeholder() + q = 1 + p + p.value = 2 + assert q == 3 + + +def test_radd2(): + p = Placeholder() + q = 1 + p + r = 2 + q + p.value = 2 + assert r == 5 + + +def test_radd3(): + p = Placeholder() + q = 1 + p + r = q + 2 + p.value = 2 + assert r == 5 + + +def test_sub1(): + p = Placeholder() + q = p - 2 + p.value = 3 + assert q == 1 + + +def test_sub2(): + p = Placeholder() + q = p - 2 + r = q - 1 + p.value = 3 + assert r == 0 + + +def test_sub3(): + p = Placeholder() + q = p - 2 + r = 2 - q + p.value = 3 + assert r == 1 + + +def test_rsub1(): + p = Placeholder() + q = 1 - p + p.value = 3 + assert q == -2 + + +def test_rsub2(): + p = Placeholder() + q = 1 - p + r = 2 - q + p.value = 2 + assert r == 3 + + +def test_rsub3(): + p = Placeholder() + q = 1 - p + r = q - 2 + p.value = 2 + assert r == -3 + + +def test_add_sub(): + p = Placeholder() + q = p + 2 + r = q - 1 + p.value = 3 + assert r == 4 + + +def test_mul1(): + p = Placeholder() + q = p * 2 + p.value = 3 + assert q == 6 + + +def test_mul2(): + p = Placeholder() + q = p * 2 + r = q * 3 + p.value = 3 + assert r == 18 + + +def test_mul3(): + p = Placeholder() + q = p * 2 + r = 3 * q + p.value = 3 + assert r == 18 + + +def test_rmul1(): + p = Placeholder() + q = 2 * p + p.value = 3 + assert q == 6 + + +def test_rmul2(): + p = Placeholder() + q = 2 * p + r = 3 * q + p.value = 2 + assert r == 12 + + +def test_rmul3(): + p = Placeholder() + q = 2 * p + r = q * 3 + p.value = 2 + assert r == 12 + + +def test_floordiv(): + p = Placeholder() + q = p // 3 + p.value = 7 + assert q == 2 + + +def test_mod(): + p = Placeholder() + q = p % 3 + p.value = 7 + assert q == 1 + + +def test_deep_equal(): + p1 = Placeholder(label='p1') + p2 = Placeholder(label='p2') + a = p1 + p2 + b = p2 + p1 + assert a == b + + +def test_deep_equal2(): + p1 = Placeholder(label='p1') + p2 = Placeholder(label='p2') + a = p1 * 5 + p2 * 2 + p2 * 3 + b = (p2 + p1) * 5 + assert a == b + + +def test_deep_equal3(): + p1 = Placeholder(label='p1') + p2 = Placeholder(label='p2') + a = (p1 * 4 + p1 * 2) * p2 + p2 * 3 + b = (1 + 2 * p1) * p2 * 3 + assert a == b + + +def test_deep_equal4(): + p1 = Placeholder(label='p1') + p2 = Placeholder(label='p1') + a = p1 + b = p2 + assert a == b diff --git a/test/webdnn_test/optimize_rule_test/traverse_test.py b/test/webdnn_test/graph_test/traverse_test.py similarity index 99% rename from test/webdnn_test/optimize_rule_test/traverse_test.py rename to test/webdnn_test/graph_test/traverse_test.py index 10739db57..3d5758550 100644 --- a/test/webdnn_test/optimize_rule_test/traverse_test.py +++ b/test/webdnn_test/graph_test/traverse_test.py @@ -189,7 +189,7 @@ def test_listup_operators_residual(): @with_setup(setup_graph_residual) -def test_listup_variables(): +def test_listup_variables_residual(): global graph, v0, v1, v2, v3 variables = listup_variables(graph) diff --git a/test/webdnn_test/graph_test/frontend_test/sub_rules_test/concat_affine_test.py b/test/webdnn_test/optimizer_test/sub_rules_test/concat_affine_test.py similarity index 99% rename from test/webdnn_test/graph_test/frontend_test/sub_rules_test/concat_affine_test.py rename to test/webdnn_test/optimizer_test/sub_rules_test/concat_affine_test.py index ebb8ec71a..744151c77 100644 --- a/test/webdnn_test/graph_test/frontend_test/sub_rules_test/concat_affine_test.py +++ b/test/webdnn_test/optimizer_test/sub_rules_test/concat_affine_test.py @@ -4,7 +4,7 @@ from nose import with_setup from test.util import FlagManager -from webdnn.frontend.sub_rules.concat_affine import ConcatAffine +from webdnn.optimizer.sub_rules.concat_affine import ConcatAffine from webdnn.graph.axis import Axis from webdnn.graph.graph import Graph from webdnn.graph.operators.axiswise_bias import AxiswiseBias diff --git a/test/webdnn_test/graph_test/frontend_test/sub_rules_test/concat_scalar_affine_test.py b/test/webdnn_test/optimizer_test/sub_rules_test/concat_scalar_affine_test.py similarity index 95% rename from test/webdnn_test/graph_test/frontend_test/sub_rules_test/concat_scalar_affine_test.py rename to test/webdnn_test/optimizer_test/sub_rules_test/concat_scalar_affine_test.py index 958ed1f73..5a158de42 100644 --- a/test/webdnn_test/graph_test/frontend_test/sub_rules_test/concat_scalar_affine_test.py +++ b/test/webdnn_test/optimizer_test/sub_rules_test/concat_scalar_affine_test.py @@ -1,7 +1,7 @@ from nose import with_setup from test.util import FlagManager -from webdnn.frontend.sub_rules.concat_scalar_affine import ConcatScalarAffine +from webdnn.optimizer.sub_rules.concat_scalar_affine import ConcatScalarAffine from webdnn.graph.graph import Graph from webdnn.graph.operators.scalar_affine import ScalarAffine from webdnn.graph.order import OrderNC diff --git a/test/webdnn_test/graph_test/frontend_test/sub_rules_test/remove_last_softmax_test.py b/test/webdnn_test/optimizer_test/sub_rules_test/remove_last_softmax_test.py similarity index 86% rename from test/webdnn_test/graph_test/frontend_test/sub_rules_test/remove_last_softmax_test.py rename to test/webdnn_test/optimizer_test/sub_rules_test/remove_last_softmax_test.py index bc3d62733..e2d299635 100644 --- a/test/webdnn_test/graph_test/frontend_test/sub_rules_test/remove_last_softmax_test.py +++ b/test/webdnn_test/optimizer_test/sub_rules_test/remove_last_softmax_test.py @@ -1,7 +1,8 @@ from nose import with_setup from test.util import FlagManager -from webdnn.frontend.sub_rules.remove_last_softmax import RemoveLastSoftmax +from webdnn.graph.axis import Axis +from webdnn.optimizer.sub_rules.remove_last_softmax import RemoveLastSoftmax from webdnn.graph.graph import Graph from webdnn.graph.operators.linear import Linear from webdnn.graph.operators.softmax import Softmax @@ -29,7 +30,7 @@ def set(self, value: bool): @with_setup(flag_manager.setup, flag_manager.teardown) def test_single_softmax(): linear = Linear('linear') - softmax = Softmax('softmax') + softmax = Softmax('softmax', axis=Axis.C) x = Variable([4, 5], OrderNC) w = Variable([4, 5], OrderNC) @@ -48,8 +49,8 @@ def test_single_softmax(): @with_setup(flag_manager.setup, flag_manager.teardown) def test_double_softmax(): linear = Linear('linear') - softmax1 = Softmax('softmax') - softmax2 = Softmax('softmax') + softmax1 = Softmax('softmax', axis=Axis.C) + softmax2 = Softmax('softmax', axis=Axis.C) x = Variable([4, 5], OrderNC) w = Variable([4, 5], OrderNC) @@ -69,9 +70,9 @@ def test_double_softmax(): @with_setup(flag_manager.setup, flag_manager.teardown) def test_internal_softmax(): linear1 = Linear('linear') - softmax1 = Softmax('softmax') + softmax1 = Softmax('softmax', axis=Axis.C) linear2 = Linear('linear') - softmax2 = Softmax('softmax') + softmax2 = Softmax('softmax', axis=Axis.C) x = Variable([4, 5], OrderNC) w1 = Variable([4, 5], OrderNC)