NVIDIA / NVIDIA/TensorRT

Inconsistencies in output when running inference on DeBERTa with disentangled attention plugin and dynamic sequence length for input

Open
#4,005 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
13.4k
Forks
2.4k
Avg merge
5d 3h
Merged PRs (30d)
2

Description

Description

When running inference with TensorRT's disentangled attention plugin on Microsoft's implementation of DeBERTa , I noticed that I get inconsistent output when running with dynamic sequence length in my inputs. This can be reproduced by using various input sequence lengths that are less than the max set for the optimization profile in the created TensorRT engine:

Running inference on engine ./test/deberta_plugin_V100_fp16.engine
Iteration 1: [[-0.015625, 0.0750732421875]]
Iteration 2: [[-0.015167236328125, 0.069580078125]]
Iteration 3: [[-0.01617431640625, 0.07696533203125]]
Iteration 4: [[-0.01483154296875, 0.08099365234375]]
Iteration 5: [[-0.0129241943359375, 0.0771484375]]
Iteration 6: [[-0.0179901123046875, 0.081298828125]]
Iteration 7: [[-0.0145416259765625, 0.0816650390625]]
Iteration 8: [[-0.0194091796875, 0.0721435546875]]
Iteration 9: [[-0.02618408203125, 0.06939697265625]]
Iteration 10: [[-0.0210113525390625, 0.07452392578125]]

I get consistent output when running inference on the model without the plugin with the same script :

Running inference on engine ./test/deberta_original_V100_fp16.engine
Iteration 1: [[-0.2298583984375, 0.19677734375]]
Iteration 2: [[-0.2298583984375, 0.19677734375]]
Iteration 3: [[-0.2298583984375, 0.19677734375]]
Iteration 4: [[-0.2298583984375, 0.19677734375]]
Iteration 5: [[-0.2298583984375, 0.19677734375]]
Iteration 6: [[-0.2298583984375, 0.19677734375]]
Iteration 7: [[-0.2298583984375, 0.19677734375]]
Iteration 8: [[-0.2298583984375, 0.19677734375]]
Iteration 9: [[-0.2298583984375, 0.19677734375]]
Iteration 10: [[-0.2298583984375, 0.19677734375]]

These results may suggest a bug with the optimization profile or the disentangled attention plugin.

Environment

TensorRT Version: 10.2

NVIDIA GPU: Tesla V100

NVIDIA Driver Version: 535.171.04

CUDA Version: 12.4

CUDNN Version: N/A

Operating System: ubuntu 20.04

Python Version (if applicable):3.8.1

Tensorflow Version (if applicable): N/A

PyTorch Version (if applicable): 1.11

Baremetal or Container (if so, version): N/A

Relevant Files

Model link:
https://huggingface.co/microsoft/deberta-v3-xsmall, pulled from transformers repository version 4.22.0

Steps To Reproduce

import torch
import tensorrt as trt
import os, sys, argparse
import numpy as np
import pycuda.driver as cuda
import pycuda.autoinit 
from transformers import DebertaV2Tokenizer, DebertaV2Config, DebertaV2ForSequenceClassification
import onnx
import onnx_graphsurgeon as gs
from onnx import TensorProto

seq_len = 4059

def export():
    parent_dir = os.path.dirname('./test/deberta.onnx')
    if not os.path.exists(parent_dir):
        os.makedirs(parent_dir)
    deberta_model = DebertaV2ForSequenceClassification.from_pretrained('microsoft/deberta-v3-xsmall')
    batch_size = 1
    seq_len = 512
    vocab_size = 128100
    deberta_model.cuda().eval()
    gpu = torch.device('cuda')
    input_ids = torch.randint(0, vocab_size, (batch_size, seq_len), dtype=torch.long, device=gpu)
    attention_mask = torch.randint(0, 2, (batch_size, seq_len), dtype=torch.long, device=gpu)
    input_names = ['input_ids', 'attention_mask']
    output_names = ['output']

    ## Set seq_len to dynamic
    dynamic_axes={'input_ids'   : {0 : 'batch_size', 1 : 'seq_len'},
                  'attention_mask'   : {0 : 'batch_size', 1 : 'seq_len'},
                  'output' : {0 : 'batch_size'}}
    
    ## Export model to onnx
    torch.onnx.export(deberta_model,
                     (input_ids, attention_mask),
                     './test/deberta.onnx',
                     export_params=True,
                     opset_version=13,
                     do_constant_folding=True,
                     input_names = input_names,
                     output_names = output_names,
                     dynamic_axes = dynamic_axes)

def remove_uint8_cast(graph):
    nodes = [node for node in graph.nodes if node.op == 'Cast' and node.attrs["to"] == TensorProto.UINT8]
    for node in nodes:
        input_node = node.i()
        input_node.outputs = node.outputs
        node.outputs.clear()
    return graph

@gs.Graph.register()
def insert_disentangled_attention(self, inputs, outputs, factor, span):
    [out.inputs.clear() for out in outputs]
    attrs = {
        "factor": 1/factor,
        "span": span
    }
    self.layer(op='DisentangledAttention_TRT', inputs=inputs, outputs=outputs, attrs=attrs)

def insert_disentangled_attention_all(graph):
    nodes = [node for node in graph.nodes if node.op == 'GatherElements']
    assert len(nodes) % 2 == 0, "No. of GatherElements nodes is not an even number!"
    layers = [(nodes[2*i+0], nodes[2*i+1]) for i in range(len(nodes)//2)]
    for l, (left,right) in enumerate(layers):
        print(f"Fusing layer {l}")
        inputs = list(left.o().o().o().o().i().inputs)[0:1] + list(left.inputs)[0:1] + list(right.inputs)[0:1]
        outputs = list(left.o().o().o().o().outputs)
        factor = 13.856406211853027
        span = 256
        graph.insert_disentangled_attention(inputs, outputs, factor, span)
    return graph

def insert_plugin():
    graph = gs.import_onnx(onnx.load('./test/deberta.onnx'))
    graph = remove_uint8_cast(graph)
    graph = insert_disentangled_attention_all(graph)
    graph.cleanup().toposort()
    onnx.save_model(gs.export_onnx(graph), './test/deberta_plugin.onnx')
    print(f"Saving modified model to ./test/deberta_plugin.onnx")

class TRTModel:
    class HostDeviceMem(object):
        def __init__(self, host_mem, device_mem):
            self.host = host_mem
            self.device = device_mem

    def __init__(self, engine_path):
        self.engine_path = engine_path
        self.logger = trt.Logger(trt.Logger.WARNING)
        self.runtime = trt.Runtime(self.logger)
        self.engine = self.load_engine()
        self.inputs, self.outputs, self.bindings, self.stream = self.allocate_buffers(self.engine)
        self.context = self.engine.create_execution_context()
        self.numpy_to_torch_dtype_dict = {
            np.int64      : torch.int64,
        }

    def load_engine(self):
        with open(self.engine_path, 'rb') as f:
            engine = self.runtime.deserialize_cuda_engine(f.read())
        return engine

    def allocate_buffers(self, engine):
        inputs = []
        outputs = []
        bindings = []
        stream = cuda.Stream()
        for i in range(engine.num_io_tensors):
            tensor_name = engine.get_tensor_name(i)
            size = trt.volume(engine.get_tensor_shape(tensor_name))
            dtype = trt.nptype(engine.get_tensor_dtype(tensor_name))
            if size == -1:
                size = 4059
            host_mem = cuda.pagelocked_empty(size, dtype) 
            device_mem = cuda.mem_alloc(host_mem.nbytes)
            bindings.append(int(device_mem))
            if engine.get_tensor_mode(tensor_name) == trt.TensorIOMode.INPUT:
                inputs.append(self.HostDeviceMem(host_mem, device_mem))
            else:
                outputs.append(self.HostDeviceMem(host_mem, device_mem))
        return inputs, outputs, bindings, stream

    def __call__(self, model_inputs: list, timing=False):
        batch_size = np.unique(np.array([i.size(dim=0) for i in model_inputs]))
        batch_size = batch_size[0]
        for i, model_input in enumerate(model_inputs):
            binding_name = self.engine.get_tensor_name(i)
            binding_dtype = trt.nptype(self.engine.get_tensor_dtype(binding_name)) 
            model_input = model_input.to(self.numpy_to_torch_dtype_dict[binding_dtype])
            cuda.memcpy_dtod_async(self.inputs[i].device, model_input.data_ptr(), model_input.element_size() * model_input.nelement(), self.stream)
        for i in range(self.engine.num_io_tensors):
            self.context.set_tensor_address(self.engine.get_tensor_name(i), self.bindings[i])
        self.context.execute_async_v3(stream_handle=self.stream.handle)
        [cuda.memcpy_dtoh_async(out.host, out.device, self.stream) for out in self.outputs]
        self.stream.synchronize()
        return [torch.from_numpy(out.host.reshape(batch_size,-1)) for out in self.outputs]
   
def build_engine():
    TRT_LOGGER = trt.Logger(trt.Logger.INFO)
    TRT_BUILDER = trt.Builder(TRT_LOGGER)
    engine_filename = '_'.join(['./test/deberta_plugin', 'V100', 'fp16']) + '.engine'
    if "EXPLICIT_BATCH" in trt.NetworkDefinitionCreationFlag.__members__.keys():
        network_creation_flag = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
    network = TRT_BUILDER.create_network(0)
    onnx_parser = trt.OnnxParser(network, TRT_LOGGER)
    onnx_parser.parse_from_file('./test/deberta_plugin.onnx')
    config = TRT_BUILDER.create_builder_config()
    profile = TRT_BUILDER.create_optimization_profile()
    profile.set_shape("input_ids", (1,1), (1,seq_len), (1,4096))
    profile.set_shape("attention_mask", (1,1), (1,seq_len), (1,4096))
    config.add_optimization_profile(profile)
    config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 4096 * (1 << 20))
    config.set_flag(trt.BuilderFlag.FP16)
    serialized_engine = TRT_BUILDER.build_serialized_network(network, config)
    with open(engine_filename, 'wb') as f:
        f.write(serialized_engine)

def test_engine():
    torch.manual_seed(42)
    engine_filename = '_'.join(['./test/deberta_plugin', 'V100', 'fp16']) + '.engine'
    model = TRTModel(engine_filename)
    batch_size = 1
    vocab = 128203
    gpu = torch.device('cuda')
    input_ids = torch.randint(0, vocab, (batch_size, seq_len), dtype=torch.long, device=gpu)
    attention_mask = torch.randint(0, 2, (batch_size, seq_len), dtype=torch.long, device=gpu)
    inputs = [input_ids, attention_mask]
    model.context.set_input_shape("input_ids", (1, seq_len))
    model.context.set_input_shape("attention_mask", (1, seq_len))
    outputs = model(inputs)
    nreps = 10
    for i in range(nreps):
        outputs = model(inputs)
        print(f'Iteration {i + 1}: {outputs[0].tolist()}')

if __name__ == "__main__":
    export()
    insert_plugin()
    build_engine()
    test_engine()

Have you tried the latest release?: Yes

Can this model run on other frameworks? For example run ONNX model with ONNXRuntime (polygraphy run <model.onnx> --onnxrt): Yes, I've tested it with onnxrt with cudnn 8.9 and CUDA 11.8, still had inconsistent output when running with plugin.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the provided Python reproduction, especially build_engine() and test_engine(), and compare ./test/deberta_plugin.onnx and the plugin engine with the original engine across dynamic sequence lengths. Investigate whether the inconsistency comes from the optimization profile or the DisentangledAttention_TRT plugin; done means identifying the source and demonstrating consistent repeated inference output.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
machine-learning, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.