# Model Acceleration [TOC] Ngày nay bên cạnh nghiên cứu ra các mô hình học sâu chính xác hơn, nhanh hơn thì việc ứng dụng đưa các mô hình học sâu vào trong các sẩn phẩm cũng không kém phần quan trọng và gặp rất nhiều thách thức. Đặc biệt trong việc chuyển từ mô hình được viết bằng framework này sang framework khác vì mỗi thư viện có các hàm và kiểu dữ liệu khác nhau. Sau khi huấn luyện mô hình, chúng ta cần chuyển đổi mô hình sao cho tương thích với hardware sử dụng (e.g, Intel CPU, Nvidia GPU, ARM CPU, etc). Khi nghiên cứu thử nghiệm mô hình mình thường sử dụng pytorch vì dễ sử dụng và cộng đồng nghiên cứu cũng dùng torch nhiều rất tiện việc tra cứu. Tuy nhiên, khi triển khai thành sản phẩm thì trong một số công cụ lại chỉ hỗ trợ tensorflow do đó để sử dụng cần phải chuyển mô hình từ torch sang tensorflow. Lúc này chúng ta cần một dạng dữ liệu chuẩn cho các hàm cũng như các dạng dữ liệu (data types) để chuyển đổi. Và ONNX là chìa khóa có thể giải quyết tất cả vấn đề trên. Practice with [colab notebook](https://colab.research.google.com/drive/1w3ETKn90O_GaLEDMH-J42fChPCpxmCB9?usp=sharing) ## Tăng tốc độ inference với Torch.compile `torch.compile` làm cho Pytorch code chạy nhanh hơn bằng việc sử dụng JIT-compiling để convert code trở thành optimized kernels nhưng lại ít yêu cầu việc thay đổi code nên việc sử dụng tương đối đơn giản nhất. ### Thử nghiệm chạy 10 iteration with densnet121 ```python! import torch import torch.nn as nn import torch.nn.functional as F import numpy as np import torchvision from torchvision import transforms import torch._dynamo from PIL import Image print(torch.cuda.is_available()) def init_model(): return torchvision.models.densenet121().to(torch.float32).cuda() def generate_data(b): return ( torch.randn(b, 3, 128, 128).to(torch.float32).cuda(), torch.randint(1000, (b,)).cuda(), ) def timed(fn): start = torch.cuda.Event(enable_timing=True) end = torch.cuda.Event(enable_timing=True) start.record() result = fn() end.record() torch.cuda.synchronize() return result, start.elapsed_time(end) / 1000 N_ITERS = 10 model = init_model() torch._dynamo.reset() model_opt = torch.compile(model, mode="reduce-overhead") eager_times = [] for i in range(N_ITERS): inp = generate_data(16)[0] with torch.no_grad(): _, eager_time = timed(lambda: model(inp)) eager_times.append(eager_time) print(f"eager eval time {i}: {eager_time}") print("~" * 10) compile_times = [] for i in range(N_ITERS): inp = generate_data(16)[0] with torch.no_grad(): _, compile_time = timed(lambda: model_opt(inp)) compile_times.append(compile_time) print(f"compile eval time {i}: {compile_time}") print("~" * 10) eager_med = np.median(eager_times) compile_med = np.median(compile_times) speedup = eager_med / compile_med assert(speedup > 1) print(f"(eval) eager median: {eager_med}, \ compile median: {compile_med}, \ speedup: {speedup}x") print("~" * 10) ``` ### Thử nghiệm với mô hình Pytorch ResNet50 cho classification. ```python! print(f"Non experimental in-tree backends: {torch._dynamo.list_backends()}") print(f"Experimental or debug in-tree backends: {torch._dynamo.list_backends(None)}") print(f"Mode in torch.compile: {torch._inductor.list_mode_options()}") ``` Determining the "best" backend depends on the specific use case and requirements. Here are some points to consider: - **Performance**: inductor and cudagraphs are typically good choices for high performance on supported hardware. - **Compatibility**: onnxrt (ONNX Runtime) and tvm can be good for deploying models across various platforms and devices. - **Stability**: Stick to the non-experimental backends like inductor, onnxrt, openxla, and tvm for stable and production-ready applications. - **Specific Needs**: Experimental backends may provide features that are not yet available in stable backends, useful for development and research purposes. The mode attribute in `torch.compile(`) specifies different optimization strategies for compiling PyTorch models: - **default**: Balances performance and overhead. - **reduce-overhead**: Minimizes Python overhead using CUDA graphs, useful for small batches. May increase memory usage due to workspace memory caching. Works only for CUDA graphs that don’t mutate inputs. Use `TORCH_LOG=perf_hints` for debugging. - **max-autotune**: Uses Triton-based matrix multiplications and convolutions with CUDA graphs enabled by default. - **max-autotune-no-cudagraphs**: Similar to "max-autotune" but without CUDA graphs. ```python! model = torchvision.models.resnet50( weights="ResNet50_Weights.IMAGENET1K_V1", progress=True ) compiled_model = torch.compile(model, backend="eager") model.eval() compiled_model.eval() ``` ```python! # Define the image preprocessing steps preprocess = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # Load an image img_path = '/content/cock.png' img = Image.open(img_path) img_t = preprocess(img) input_tensor = img_t.unsqueeze(0) # Perform inference with torch.inference_mode(): output = model(input_tensor) output_ = compiled_model(input_tensor) print(torch.argmax(output, dim=1), torch.argmax(output_, dim=1)) ``` ```python! %%time output = model(input_tensor) ``` ```python! %%time with torch.inference_mode(): output = compiled_model(input_tensor) ``` ## Tăng tốc độ với Torch Scripting Khi mô hình gặp lỗi khi chuyển sang format ONNX, ta có thể sử dụng Torch Script. Đây là một phương pháp để chuyển mô hình Pytorch về dạng serializable và optmizable. Chỉ khi ở dạng serialized, script này có thể chạy không cần môi trường python, dependencies phức tạp. Ví dụ có thể inference [TorchScript models ở C++](https://pytorch.org/tutorials/advanced/cpp_export.html). ### SwinTransformer-B Conversion Ta sẽ thực hiện chuyển đổi SwinTransformer-B dưới dạng Torch Script. ```python! model = torchvision.models.swin_b( weights="Swin_B_Weights.IMAGENET1K_V1", progress=True ) torch.save(model, "swin-b.pt") ``` ```python! model = torch.load('swin-b.pt') scripted_model = torch.jit.script(model) print(scripted_model) scripted_model.save('scripted-swin-b.pt') ``` Benchmark accuracy: Xem [ImageNet1k class list](https://deeplearning.cms.waikato.ac.nz/user-guide/class-maps/IMAGENET/) để check độ chính xác ```python! def preprocess(path): augment = transforms.Compose([ transforms.Resize(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) image = Image.open(path) image = augment(image) return image.unsqueeze(0) inputs = preprocess("/content/junco.png") model.eval() scripted_model.eval() output = model(inputs) output_ = scripted_model(inputs) print(torch.argmax(output, dim=1), torch.argmax(output_, dim=1)) ``` Benchmark time ```python! inputs = [torch.randn(24, 3, 224, 224), torch.randn(24, 3, 224, 224), torch.randn(24, 3, 224, 224), torch.randn(24, 3, 224, 224)] ``` ```python! %%timeit with torch.inference_mode(): for input in inputs: output = model(input) ``` ```python! %%timeit for input in inputs: output = scripted_model(inputs) ``` ## Open Neural Network Exchange (ONNX) **Giới thiệu về ONNX:** ONNX được xem là một ngôn ngữ đại diện cho công cụ toán học và thường được biểu diễn dưới dạng đồ thị (graph). Với ONNX, ta có thể xây dựng một quy trình deploy model độc lập hoàn toàn với các framework huấn luyện model (e.g, TensorFlow, Pytorch, etc). **Cách hoạt động của ONNX**: - Xây dụng một đồ thị ONNX nghĩa là sử dụng toán tử hay ngôn ngữ riêng của ONNX. Một đồ thị sẽ có những đỉnh (nodes), ta hình dung nodes trong đồ thị bằng phép toán đơn giản. \begin{gather*} y = x \times a + c \\ y = r + c \end{gather*} - Ta có các nodes $x$, node $c$, node $r$ là kết quả trung gian của phép tính trên, và node $y$ là kết quả. Và các phép tính $\times$ và $+$ sẽ là các operator trên đường nối các nodes. - Ta đều biết, để chạy mô hình, ta cần có môi trường cài đặt những dependencies cần thiết. Nhưng với ONNX, ta chỉ cần một runtime có sẵn bởi ONNX để chạy cái graph là một dãy những phép tính toán học trên những con số. Và runtime này có thể được code ở bất kỳ ngôn ngữ nào (e.g, C, java, python, javascript, C#, etc) để thực hiện inference với mô hình. - Một điều lưu ý khi sử dụng ONNX là hạn chế sử dụng các câu lệnh `if/else/loop` vì ONNX thực chất chỉ là một cấu trúc cây (graph) thực hiện các phép tính toán trên tensors (ma trận/vector) nên sử dụng các câu lệnh trên sẽ làm cấu trúc cây trở nên chồng chéo phức tạp. $\rightarrow$ Tham khảo cách code ONNX trong [notebook colab](https://colab.research.google.com/drive/1w3ETKn90O_GaLEDMH-J42fChPCpxmCB9?usp=sharing) này. ## ONNX Runtime **Khái niệm:** ONNX Runtime is a performance-focused engine for ONNX models, which inferences efficiently across multiple platforms and hardware (Windows, Linux, and Mac and on both CPUs and GPUs) Ta không cần viết code với "ngôn ngữ" onnx từ đầu một cách thủ công lại như trên vì ta có thể sử dụng `ONNX Runtime` để chuyển đổi kiến trúc mô hình từ các framework `TensorFlow/Pytorch`. Hiện nay `ONNX Runtime` đã có thể tích hợp sẵn trong các thư viện ML/DL, ví dụ `keras2onnx`, `tf2onnx`, `torch.onnx`. Và việc inference đã được hỗ trợ bởi `onnxruntime.InferenceSession`. ### Sử dụng torch.onnx.export Ta có hai bước cần thực hiện khi chuyển đổi sang onnx là 1.conversion và 2. inference. Ví dụ mẫu với mô hình DenseNet pretrained trên bộ dữ liệu ImageNet1K với thư viện torchvision. #### Bước 1: Convert model ```python! model = torchvision.models.densenet161( weights="DenseNet161_Weights.IMAGENET1K_V1", progress=True ) ``` **Constant folding** is an optimization technique used in compiler theory, including the context of neural network models. Here’s what it does: - Identify Constants: The process scans the computational graph to identify operations that involve only constants. - Compute at Export Time: Instead of keeping these constant computations as part of the graph, the values are precomputed during the export process. - Simplify the Graph: The precomputed values replace the original operations in the graph, simplifying it and potentially reducing the computational load during runtime. **Benefits**: - Reduced Computation: By precomputing constant values, the model requires fewer computations during inference, which can lead to faster execution. - Smaller Model Size: Simplifying the graph by removing unnecessary operations can reduce the overall size of the model. - Optimization: It helps in optimizing the model for better performance on various hardware by eliminating redundant calculations. ```python! input = torch.randn(12, 3, 224, 224, requires_grad=True) torch.onnx.export ( model, input, "densenet161.onnx", export_params=False, opset_version=10, input_names=["input"], output_names=["output"], do_constant_folding=True, dynamic_axes={ "input": {0: "batch_size"}, "output": {0: "batch_size"} } ) ``` #### Bước 2: Inference với converted model ```bash! !pip install onnxruntime-gpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/ ``` ```python! import onnxruntime as ort # Preprocessing def preprocess(path): augment = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) image = Image.open(path) image = augment(image) return image.unsqueeze(0).numpy() inputs = preprocess("/content/junco.png") # Prepare providers providers = [ 'TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider' ] ort_session = ort.InferenceSession("/content/densenet161.onnx", providers=providers) inp = {ort_session.get_inputs()[0].name: inputs} out = ort_session.run(None, inp) # Postprocessing def postprocess(out): idx = np.argmax(out[0]) return idx print(postprocess(out)) ``` List được sắp xếp theo thứ tự ưu tiên, ngoài ra ta có thể specify thêm thông tin cho từng providers, đọc thêm tại [documentation](https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html). Lưu ý để thực hiện inference với TensorRT, ta cần install đúng version của `cuda, cudnn, tensorrt`, tham khảo documentation này để tìm hiểu về Docker để cài TensorRT thành công. Dưới đây là một ví dụ về provider config cho TensorRT backend. ```python! providers = [ ('TensorrtExecutionProvider', { 'device_id': 0, # Select GPU to execute 'trt_max_workspace_size': 2147483648, # Set GPU memory usage limit 'trt_fp16_enable': True, # Enable FP16 precision 'trt_engine_cache_enable': True, 'trt_engine_cache_path': 'Engine/onnx_models', }), ] ``` ### Cách simpify mô hình onnx với onnxsim Cơ chế khởi tạo sơ đồ phép tính (graph) của ONNX đôi khi có cấu trúc quá phức tạp và có những phần không cần thiết, ta có thể đơn giản hóa để làm gọn graph và làm mô hình nhẹ bằng thư viện [onnxsim](https://github.com/daquexian/onnx-simplifier). - Cài thư viện ```python! !pip install onnxsim ``` ``` !onnxsim /content/densenet161.onnx /content/simplified_densenet161.onnx ``` ``` Simplifying... Finish! Here is the difference: ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓ ┃ ┃ Original Model ┃ Simplified Model ┃ ┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩ │ AveragePool │ 3 │ 3 │ │ BatchNormalization │ 82 │ 82 │ │ Concat │ 82 │ 78 │ │ Constant │ 569 │ 569 │ │ Conv │ 160 │ 160 │ │ Flatten │ 1 │ 1 │ │ Gemm │ 1 │ 1 │ │ GlobalAveragePool │ 1 │ 1 │ │ MaxPool │ 1 │ 1 │ │ Relu │ 161 │ 161 │ │ Model Size │ 110.3MiB │ 110.3MiB │ └────────────────────┴────────────────┴──────────────────┘ ``` ```python! import onnx from onnxsim import simplify model = onnx.load("/content/densenet161.onnx") model_simp, check = simplify(model) assert check, "Simplified ONNX model could not be validated" onnx.save(model_simp, "/content/densenet161_sim.onnx") ``` ### ONNX with OpenVINO ``` !pip install openvino --no-deps ``` ```python! import openvino as ov input = torch.tensor(12, 3, 224, 224) core = ov.Core() compiled_model = core.compile_model("model.onnx", "CPU") infer_request = compiled_model.create_infer_request() ``` ```python! # Warmup step input_tensor = ov.Tensor(array=inputs, shared_memory=True) infer_request.set_input_tensor(input_tensor) ``` ```python! %%timeit output_tensor = infer_request.infer() ``` ## Precision and Quantization Có hai cách để nén một mô hình nặng trở nên nhẹ hơn: low-rank precision và quantization. ### Precision: - **Half-precision**: chuyển bộ trọng số $W$ từ kiểu dữ liệu `float32` (FP32) sang kiểu dữ liệu `float16` (FP16). Điều này giúp tối ưu một nửa bộ nhớ (reduced Memory Usage) và tăng hiệu năng (increased throughput) của hardware thích hợp với tính toán FP16, nhưng sẽ mất precision nên cần benchmark lại để đánh giá. - **Mixed precision**: kết hợp một cách cân bằng giữa hai loại FP16 và FP32 để vừa tối ưu bộ nhớ nhưng vẫn tối thiểu sai số nhất có thể Sử dụng mixed precision với `neural-compressor.mix_precision` (intel) Tham khảo bảng hỗ trợ của neural-compressor: <table class="center"> <thead> <tr> <th>Framework</th> <th>Backend</th> <th>Backend Library</th> <th>Backend Value</th> <th>Support Device(cpu as default)</th> <th>Support BF16</th> <th>Support FP16</th> </tr> </thead> <tbody> <tr> <td rowspan="2" align="left">PyTorch</td> <td align="left">FX</td> <td align="left">FBGEMM</td> <td align="left">"default"</td> <td align="left">cpu</td> <td align="left">&#10004;</td> <td align="left">&#10006;</td> </tr> <tr> <td align="left">IPEX</td> <td align="left">OneDNN</td> <td align="left">"ipex"</td> <td align="left">cpu</td> <td align="left">&#10004;</td> <td align="left">&#10006;</td> </tr> <tr> <td rowspan="4" align="left">ONNX Runtime</td> <td align="left">CPUExecutionProvider</td> <td align="left">MLAS</td> <td align="left">"default"</td> <td align="left">cpu</td> <td align="left">&#10006;</td> <td align="left">&#10006;</td> </tr> <tr> <td align="left">TensorrtExecutionProvider</td> <td align="left">TensorRT</td> <td align="left">"onnxrt_trt_ep"</td> <td align="left">gpu</td> <td align="left">&#10006;</td> <td align="left">&#10006;</td> </tr> <tr> <td align="left">CUDAExecutionProvider</td> <td align="left">CUDA</td> <td align="left">"onnxrt_cuda_ep"</td> <td align="left">gpu</td> <td align="left">&#10004;</td> <td align="left">&#10004;</td> </tr> <tr> <td align="left">DnnlExecutionProvider</td> <td align="left">OneDNN</td> <td align="left">"onnxrt_dnnl_ep"</td> <td align="left">cpu</td> <td align="left">&#10004;</td> <td align="left">&#10006;</td> </tr> <tr> <td rowspan="2" align="left">Tensorflow</td> <td align="left">Tensorflow</td> <td align="left">OneDNN</td> <td align="left">"default"</td> <td align="left">cpu</td> <td align="left">&#10004;</td> <td align="left">&#10006;</td> </tr> <tr> <td align="left">ITEX</td> <td align="left">OneDNN</td> <td align="left">"itex"</td> <td align="left">cpu | gpu</td> <td align="left">&#10004;</td> <td align="left">&#10006;</td> </tr> <tr> <td align="left">MXNet</td> <td align="left">OneDNN</td> <td align="left">OneDNN</td> <td align="left">"default"</td> <td align="left">cpu</td> <td align="left">&#10004;</td> <td align="left">&#10006;</td> </tr> </tbody> </table> ``` !pip install neural-compressor ``` ```python! from neural_compressor import mix_precision from neural_compressor.config import MixedPrecisionConfig ``` Compress model trực tiếp từ model pytorch (chỉ có CPU) ```python! conf = MixedPrecisionConfig( backend="ipex", device="cpu", precisions="bf16", ) torch_model = torch.load("/content/swin-b.pt") converted_model = mix_precision.fit(torch_model, conf=conf) ``` Compress model.onnx với GPU ```python! conf = MixedPrecisionConfig( backend="onnxrt_cuda_ep", device="gpu", precisions="fp16", ) # chỉ sử dụng được với GPU cho model ỏ onnx format. onnx_model = onnx.load("/content/densenet161_sim.onnx") converted_model = mix_precision.fit(onnx_model, conf=conf) converted_model.save("mixed_precision_densenet161_sim.onnx") ``` Compress model.onnx với CPU ```python! conf = MixedPrecisionConfig( backend="onnxrt_dnnl_ep", device="cpu", precisions="bf16", ) onnx_model = onnx.load("/content/densenet161_sim.onnx") converted_model = mix_precision.fit(onnx_model, conf=conf) converted_model.save("mixed_precision_densenet_cpu.onnx") ``` ### Quantization Quantization là một kỹ thuật tối ưu nâng cao để tăng tốc độ inference/training. Nó giúp giảm số bit lưu trữ bằng cách chuyển số thực sang dạng số nguyên `int8`, `int4` nhưng không làm mất đi accuracy. Có thể phân loại quantization bằng hai cách. - Chia theo đặc tính: Affine Quantization (asymmetric) và Scale Quantization (symmetric). - Chia theo cách sử dụng: Post-Training Dynamic Quantization, Post-Training Static Quantization, Quantization-Aware Training. #### Sử dụng Sử dụng trực tiếp `torch.quantization`: \ **Post-Training Quantization** ```python! import torch model = torch.load('/content/swin-b.pt') model.eval() quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtype=torch.qint8 ) torch.save(quantized_model, 'quantized-swin-b.pt') ``` **Quantization-Aware Training** ```python! # Enable quantization-aware training model.qconfig = torch.quantization.default_qconfig # Convert the model to quantized version quantized_model = torch.quantization.convert(model, inplace=False) # Define the loss function and optimizer criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(quantized_model.parameters(), lr=0.01, momentum=0.9) # Train the model for epoch in range(5): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): # Get the inputs and labels inputs, labels = data inputs, labels = inputs.to(device), labels.to(device) # Zero the gradients optimizer.zero_grad() # Forward pass outputs = quantized_model(inputs) # Compute the loss loss = criterion(outputs, labels) # Backward pass loss.backward() # Optimize optimizer.step() # Evaluate the model correct = 0 total = 0 with torch.no_grad(): for data in testloader: inputs, labels = data inputs, labels = inputs.to(device), labels.to(device) outputs = quantized_model(inputs) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total)) # Save the model torch.save(quantized_model, 'mnist_model_quantized.pt') ``` Sử dụng `neural-compressor.quantization:` ```python! from neural_compressor.config import PostTrainingQuantConfig from neural_compressor import quantization ``` Post-Training Quantization (w/o Accuracy Aware Tuning) ```python val_dataset = ... val_loader = ... model = ... conf = (PostTrainingQuantConfig()) quantized_model = quantization.fit( model=model, conf=conf, calib_dataloader=val_dataloader, ) ``` Post-Training Quantization (with Accuracy Aware Tuning) ```python def validate(val_loader, model, criterion, args): ... return top1.avg quantized_model = quantization.fit( model=model, conf=conf, calib_dataloader=val_dataloader, eval_func=validate, ) ``` Quantization-Aware Training ```python from neural_compressor import QuantizationAwareTrainingConfig from neural_compressor.training import prepare_compression conf = QuantizationAwareTrainingConfig() compression_manager = prepare_compression(model, conf) compression_manager.callbacks.on_train_begin() model = compression_manager.model train_func(model) compression_manager.callbacks.on_train_end() compression_manager.save("./output") ``` Chọn backend cho vào bên trong `conf`: <table class="center"> <thead> <tr> <th>Framework</th> <th>Backend</th> <th>Backend Library</th> <th>Backend Value</th> <th>Support Device</th> </tr> </thead> <tbody> <tr> <td rowspan="2" align="left">PyTorch</td> <td align="left">FX</td> <td align="left">FBGEMM</td> <td align="left">"default"</td> <td align="left">cpu</td> </tr> <tr> <td align="left">IPEX</td> <td align="left">OneDNN</td> <td align="left">"ipex"</td> <td align="left">cpu | xpu</td> </tr> <tr> <td rowspan="5" align="left">ONNX Runtime</td> <td align="left">CPUExecutionProvider</td> <td align="left">MLAS</td> <td align="left">"default"</td> <td align="left">cpu</td> </tr> <tr> <td align="left">TensorrtExecutionProvider</td> <td align="left">TensorRT</td> <td align="left">"onnxrt_trt_ep"</td> <td align="left">gpu</td> </tr> <tr> <td align="left">CUDAExecutionProvider</td> <td align="left">CUDA</td> <td align="left">"onnxrt_cuda_ep"</td> <td align="left">gpu</td> </tr> <tr> <td align="left">DnnlExecutionProvider</td> <td align="left">OneDNN</td> <td align="left">"onnxrt_dnnl_ep"</td> <td align="left">cpu</td> </tr> <tr> <td align="left">DmlExecutionProvider*</td> <td align="left">OneDNN</td> <td align="left">"onnxrt_dml_ep"</td> <td align="left">npu</td> </tr> <tr> <td rowspan="2" align="left">Tensorflow</td> <td align="left">Tensorflow</td> <td align="left">OneDNN</td> <td align="left">"default"</td> <td align="left">cpu</td> </tr> <tr> <td align="left">ITEX</td> <td align="left">OneDNN</td> <td align="left">"itex"</td> <td align="left">cpu | gpu</td> </tr> <tr> <td align="left">MXNet</td> <td align="left">OneDNN</td> <td align="left">OneDNN</td> <td align="left">"default"</td> <td align="left">cpu</td> </tr> </tbody> </table> <br> <br>