Implantação do modelo de IA - implementação em Python da quantificação INT8 do modelo TensorRT

Implantação do modelo de IA: implementação em Python da quantificação INT8 do modelo TensorRT

Este artigo foi publicado pela primeira vez na conta pública [DeepDriving], fique atento.

Visão geral

32Atualmente, os parâmetros dos modelos de aprendizado profundo são basicamente expressos em ponto flutuante de bits ( ) durante a fase de treinamento FP32, para que uma faixa dinâmica maior possa ser usada para atualizar os parâmetros durante o processo de treinamento. Porém, na etapa de inferência, FP32a precisão utilizada consumirá mais recursos computacionais e espaço de memória. Por esse motivo, ao implantar o modelo, muitas vezes é utilizado um método para reduzir a precisão do modelo, utilizando ponto flutuante de bit ( ) ou sinal 16de bit FP16. 8inteiro ( INT8) Para representar. Geralmente não há perda de precisão na FP32conversão para , mas a conversão para 2 pode causar uma perda maior de precisão, especialmente quando os pesos do modelo são distribuídos em uma grande faixa dinâmica.FP16FP32INT8

Embora haja uma certa perda de precisão, a conversão INT8também trará muitos benefícios, como redução CPUda ocupação de espaço de armazenamento e memória, melhoria do rendimento computacional, etc. Isso é muito significativo em plataformas embarcadas com recursos computacionais limitados.

Para converter o tensor de parâmetro do modelo de FP32para INT8, ou seja, o intervalo para o qual o intervalo dinâmico do tensor de ponto flutuante é mapeado [-128,127], você pode usar a seguinte fórmula:

xq = C lip (Round (xf / escala)) x_{q}=Clip(Round(x_{f}/scale))xq=Clipe ( R o u n d ( x _ _f/ sc a l e ))

Entre eles, C lip ClipCl i pR ound RoundRound representa operações de truncamento e arredondamento , respectivamente . Como pode ser visto na fórmula acima,FP32a conversãoINT8é definir umaescala de fator de escalaescala é usada para mapeamento. Este processo de mapeamento é chamado de quantização. A fórmula acima é uma fórmula de quantização simétrica .

A chave para a quantização é encontrar um fator de escala apropriado para que a precisão do modelo quantizado seja o mais próximo possível do modelo original. Existem duas maneiras de quantificar um modelo:

  • A quantização pós-treinamento ( ) consiste em calcular o fator de escala por meio de um processo de calibração ( ) Post-training quantization,PTQapós o modelo ser treinado para atingir o processo de quantização.Calibration

  • O treinamento com reconhecimento de quantização ( Quantization-aware training,QAT) calcula o fator de escala durante o processo de treinamento do modelo, permitindo que o erro de precisão causado pelas operações de quantização e quantização inversa seja compensado durante o processo de treinamento.

Este artigo apresenta apenas como chamar a TensorRTinterface Pythonpara obter INT8quantificação. Quanto INT8ao conhecimento teórico de quantificação, por envolver muito conteúdo, escreverei um artigo especial para apresentá-lo quando tiver tempo.

Implementação específica da quantificação TensorRT INT8

Calibrador no TensorRT

Durante o processo de quantização pós-treinamento, TensorRTé necessário calcular o fator de escala de cada tensor do modelo, processo denominado calibração. O processo de calibração requer o fornecimento de dados representativos para TensorRTexecutar o modelo neste conjunto de dados e, em seguida, coletar estatísticas para cada tensor para encontrar um fator de escala ideal. Encontrar o fator de escala ideal requer equilibrar as duas fontes de erro: erro de discretização (que se torna maior à medida que o intervalo representado por cada valor quantizado aumenta) e erro de truncamento (cujos valores são restritos aos limites do intervalo representável), fornecendo TensorRTvários calibradores diferentes:

  • IInt8EntropyCalibrator2 : O calibrador de entropia atualmente recomendado, por padrão a calibração ocorre antes da fusão da camada, recomendado para uso CNNem modelos.

  • IInt8MinMaxCalibrator : Este calibrador utiliza toda a faixa da distribuição de ativação para determinar o fator de escala. Por padrão a calibração ocorre antes da fusão das camadas, recomendado NLPem modelos para a tarefa.

  • IInt8EntropyCalibrator : Este calibrador é TensorRTo calibrador de entropia original. Por padrão, a calibração ocorre após a fusão da camada e seu uso está atualmente obsoleto.

  • IInt8LegacyCalibrator : Este calibrador requer parametrização do usuário, por padrão a calibração ocorre após a fusão da camada e não é recomendado.

TensorRTAo construir INT8um mecanismo modelo, as seguintes etapas são executadas:

  1. Construa um 32mecanismo de modelo de bits, execute-o no conjunto de dados de calibração e registre um histograma da distribuição dos valores de ativação para cada tensor;
  2. Construa uma tabela de calibração a partir do histograma e calcule um fator de escala para cada tensor;
  3. Construa um mecanismo com base na tabela de calibração e na definição do modelo INT8.

O processo de calibração pode ser lento, mas a tabela de calibração gerada na segunda etapa pode ser gerada em um arquivo e reutilizada. Se o arquivo da tabela de calibração já existir, o calibrador lerá a tabela de calibração diretamente do arquivo sem executar o procedimento anterior. dois passos. Além disso, diferentemente dos arquivos do mecanismo, as tabelas de calibração podem ser usadas em várias plataformas. Portanto, durante a implantação real do modelo, podemos primeiro GPUgerar a tabela de calibração em um computador de uso geral e depois Jetson Nanousá-la em uma plataforma embarcada. Por conveniência de codificação, podemos usar Pythonprogramação para implementar INT8o processo de quantização para gerar uma tabela de calibração.

Implementação
1. Carregar dados de calibração

Primeiro defina uma classe de carregamento de dados para carregar dados de calibração. Os dados de calibração aqui são uma JPGimagem no formato. Depois que a imagem é lida, ela precisa ser dimensionada, normalizada, troca de canais e outras operações de pré-processamento de acordo com os requisitos de dados de entrada do modelo:

class CalibDataLoader:
    def __init__(self, batch_size, width, height, calib_count, calib_images_dir):
        self.index = 0
        self.batch_size = batch_size
        self.width = width
        self.height = height
        self.calib_count = calib_count
        self.image_list = glob.glob(os.path.join(calib_images_dir, "*.jpg"))
        assert (
            len(self.image_list) > self.batch_size * self.calib_count
        ), "{} must contains more than {} images for calibration.".format(
            calib_images_dir, self.batch_size * self.calib_count
        )
        self.calibration_data = np.zeros((self.batch_size, 3, height, width), dtype=np.float32)

    def reset(self):
        self.index = 0

    def next_batch(self):
        if self.index < self.calib_count:
            for i in range(self.batch_size):
                image_path = self.image_list[i + self.index * self.batch_size]
                assert os.path.exists(image_path), "image {} not found!".format(image_path)
                image = cv2.imread(image_path)
                image = Preprocess(image, self.width, self.height)
                self.calibration_data[i] = image
            self.index += 1
            return np.ascontiguousarray(self.calibration_data, dtype=np.float32)
        else:
            return np.array([])

    def __len__(self):
        return self.calib_count

O código da operação de pré-processamento é o seguinte:

def Preprocess(input_img, width, height):
    img = cv2.cvtColor(input_img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, (width, height)).astype(np.float32)
    img = img / 255.0
    img = np.transpose(img, (2, 0, 1))
    return img
2. Implemente o calibrador

Para implementar a função de um calibrador, você precisa herdar TensorRTuma das quatro classes de calibrador fornecidas e, em seguida, substituir vários métodos do calibrador pai:

  • get_batch_size: batchtamanho usado para obter
  • get_batch: usado para obter batchdados de um
  • read_calibration_cache: Usado para ler a tabela de calibração de um arquivo
  • write_calibration_cache: Usado para gravar a tabela de calibração da memória em um arquivo

Como é o modelo que preciso quantificar CNN, opto por herdar IInt8EntropyCalibrator2o calibrador:

import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit

class Calibrator(trt.IInt8EntropyCalibrator2):
    def __init__(self, data_loader, cache_file=""):
        trt.IInt8EntropyCalibrator2.__init__(self)
        self.data_loader = data_loader
        self.d_input = cuda.mem_alloc(self.data_loader.calibration_data.nbytes)
        self.cache_file = cache_file
        data_loader.reset()

    def get_batch_size(self):
        return self.data_loader.batch_size

    def get_batch(self, names):
        batch = self.data_loader.next_batch()
        if not batch.size:
            return None
        # 把校准数据从CPU搬运到GPU中
        cuda.memcpy_htod(self.d_input, batch)

        return [self.d_input]

    def read_calibration_cache(self):
        # 如果校准表文件存在则直接从其中读取校准表
        if os.path.exists(self.cache_file):
            with open(self.cache_file, "rb") as f:
                return f.read()

    def write_calibration_cache(self, cache):
        # 如果进行了校准,则把校准表写入文件中以便下次使用
        with open(self.cache_file, "wb") as f:
            f.write(cache)
            f.flush()
3. Gere mecanismo INT8

FP32Já apresentei o processo de geração de motores de modelo em um artigo anterior, mas naquele artigo ele foi C++implementado. Chamar Pythona implementação da interface é realmente mais simples. O código específico é o seguinte:

def build_engine():
    builder = trt.Builder(TRT_LOGGER)
    network = builder.create_network(1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
    config = builder.create_builder_config()
    parser = trt.OnnxParser(network, TRT_LOGGER)
    assert os.path.exists(onnx_file_path), "The onnx file {} is not found".format(onnx_file_path)
    with open(onnx_file_path, "rb") as model:
        if not parser.parse(model.read()):
            print("Failed to parse the ONNX file.")
            for error in range(parser.num_errors):
                print(parser.get_error(error))
            return None

    print("Building an engine from file {}, this may take a while...".format(onnx_file_path))

    # build tensorrt engine
    config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 * (1 << 30))  
    if mode == "INT8":
        config.set_flag(trt.BuilderFlag.INT8)
        calibrator = Calibrator(data_loader, calibration_table_path)
        config.int8_calibrator = calibrator
    else mode == "FP16":
        config.set_flag(trt.BuilderFlag.FP16)

    engine = builder.build_engine(network, config)
    if engine is None:
        print("Failed to create the engine")
        return None
    with open(engine_file_path, "wb") as f:
        f.write(engine.serialize())

    return engine

O código acima é OnnxParserusado primeiro para analisar o modelo e depois configdefinir a precisão do mecanismo. Se você estiver construindo INT8um mecanismo, será necessário definir as configurações correspondentes Flage passar nele o objeto calibrador implementado anteriormente, para que os TensorRTdados de calibração sejam lidos automaticamente para gerar uma tabela de calibração ao construir o mecanismo.

Resultado dos testes

Para verificar INT8o efeito da quantificação, fiz um teste comparativo em YOLOv5vários modelos que utilizei na placa gráfica. GeForce GTX 1650 TiOs resultados do teste demorado de inferência com diferentes precisões são os seguintes:

Modelo Insira as dimensões Precisão do modelo Tempo de raciocínio (ms)
yolov5s.onnx 640x640 INT8 7
yolov5m.onnx 640x640 INT8 10
yolov5l.onnx 640x640 INT8 15
yolov5s.onnx 640x640 FP32 12
yolov5m.onnx 640x640 FP32 23
yolov5l.onnx 640x640 FP32 45

yolov5lOs resultados de detecção de alvo do modelo FP32e INT8precisão são mostrados nas duas imagens a seguir:

Pode-se observar que os resultados dos testes ainda estão relativamente próximos.

Referências

  1. https://developer.nvidia.com/zh-cn/blog/tensorrt-int8-cn/
  2. https://developer.nvidia.com/blog/chiev-fp32-accuracy-for-int8-inference-using-quantization-aware-training-with-tensorrt/
  3. https://docs.nvidia.com/deeplearning/tensorrt/archives/tensorrt-861/developer-guide/index.html#working-with-int8
  4. https://github.com/xuanandsix/Tensorrt-int8-quantization-pipline.git

Acho que você gosta

Origin blog.csdn.net/weixin_44613415/article/details/131850160
Recomendado
Clasificación