Quantification StableHLO

Types de quantification dans StableHLO

La quantification est une technique permettant d'optimiser les modèles de machine learning en convertissant les nombres à virgule flottante (comme ceux utilisés dans les modèles d'origine) en nombres entiers de précision inférieure. Cela réduit l'utilisation de la mémoire et accélère les calculs, ce qui rend les modèles plus efficaces pour le déploiement sur des appareils aux ressources limitées.

La quantification StableHLO suit la spécification de quantification LiteRT, en utilisant un schéma de quantification uniforme compatible avec la quantification par Tensor et par axe. Il hérite de son expression de type du dialecte Quant de MLIR, ce qui permet de représenter les types de données quantifiés de manière standardisée.

La quantification uniforme mappe les valeurs à virgule flottante à des nombres entiers à l'aide d'une taille de pas uniforme, ce qui donne des valeurs quantifiées uniformément espacées. Pour ce faire, une relation affine est utilisée avec deux paramètres de quantification clés.

La quantification uniforme simplifie la représentation des nombres à virgule flottante en les mappant à des entiers uniformément espacés. Ce mappage est obtenu grâce à une transformation affine qui utilise deux paramètres clés : scale et zero point. L'échelle détermine la taille du pas entre les valeurs quantifiées consécutives. Une échelle plus petite signifie que les valeurs quantifiées sont plus proches les unes des autres. Le point zéro définit la valeur entière qui représente zéro dans l'espace à virgule flottante d'origine.

Dans la quantification uniforme, la relation entre la valeur à virgule flottante d'origine (real_value) et la valeur entière quantifiée (quantized_value) est la suivante :

real_value = scale * (quantized_value - zero_point)

Quantification par tenseur

Dans la quantification par tenseur, une seule échelle et un seul point zéro sont utilisés pour toutes les valeurs du tenseur. Un type quantifié par tenseur est exprimé dans StableHLO comme suit :

quant.uniform scale:zero_point>

Exemple : !quant.uniform<i8:f32, 0.01:50>

Il s'agit d'un entier de 8 bits (i8) utilisé pour stocker un nombre à virgule flottante de 32 bits (f32) avec une échelle de 0.01 et un point zéro de 50.

Quantification par axe

La quantification par axe offre une approche plus précise que la quantification par Tensor. Au lieu d'utiliser une seule échelle et un seul point zéro pour l'ensemble du Tensor, la quantification par axe attribue des échelles et des points zéro distincts aux tranches le long d'une dimension spécifique quantized_dimension du Tensor. Cela est particulièrement utile lorsque les valeurs varient considérablement selon les dimensions, ce qui permet de mieux préserver les informations et la précision.

Prenons l'exemple d'un Tensor t avec des tailles de dimensions [4, 3, 2]. Nous choisissons de quantifier ce Tensor le long de la deuxième dimension (quantized_dimension = 1). Cela signifie que nous aurons trois tranches (puisque la deuxième dimension a une taille de 3), chacune avec sa propre échelle et son propre point zéro :

t[:, 0, :]: This slice gets scale[0] and zero_point[0].
t[:, 1, :]: This slice gets scale[1] and zero_point[1].
t[:, 2, :]: This slice gets scale[2] and zero_point[2].

Dans StableHLO, le type quantifié par axe est exprimé comme suit :

quant.uniform {scale0:zero_point0, scale1:zero_point1, ...}>

où la longueur de scale:zero_point correspond au nombre de tranches le long de quantized_dimension du Tensor contenant.

Exemple : tensor<4x3x2x!quant.uniform<i8:f32:1, {0.2:20, 0.1:10, 0.3:30}>>

Passes de quantification dans StableHLO

StableHLO fournit plusieurs passes de compilation qui permettent différentes transformations et optimisations liées à la quantification, ce qui vous donne la possibilité de gérer les modèles quantifiés de la manière qui vous convient le mieux. Voici les pass disponibles :

stablehlo-legalize-qdq-to-quantized-op

Ce pass fusionne un schéma courant dans les modèles quantifiés, une opération de déquantification suivie d'une opération à virgule flottante, puis d'une opération de quantification, en une seule opération quantifiée. Détails

stablehlo-legalize-quantized-op-to-qdq

Cette passe fait l'inverse de la précédente. Elle décompose une opération quantifiée en sa séquence équivalente d'opérations de déquantification, à virgule flottante et de quantification. Détails

stablehlo-legalize-quant-to-math

Ce pass convertit les opérations StableHLO sur les types quantifiés en opérations équivalentes sur les types entiers. Il implémente essentiellement l'arithmétique de quantification à l'aide d'opérations mathématiques standards. Cette décomposition est utile pour les systèmes qui ne prennent pas en charge la quantification de manière native, mais qui peuvent toujours utiliser l'arithmétique de quantification pour exprimer la sémantique des modèles quantifiés. Détails

stablehlo-quant-legalize-to-tosa-rescale

StableHLO permet de légaliser les opérations quantifiées dans leurs représentations correspondantes dans le dialecte TOSA. Cette légalisation facilite la compatibilité et l'interopérabilité entre StableHLO et TOSA. Ce pass convertit stratégiquement les opérations quantifiées StableHLO en une combinaison d'opérations StableHLO et TOSA, le dialecte TOSA étant principalement utilisé pour l'opération rescale. L'opération tosa.rescale joue un rôle essentiel dans l'ajustement de l'échelle et du point zéro des valeurs quantifiées, ce qui permet une représentation précise des données quantifiées dans le framework TOSA. détails

tosa-rescale-legalize-to-stablehlo

Ce pass réécrit les opérations de remise à l'échelle TOSA en opérations mathématiques primitives StableHLO. L'un des principaux cas d'utilisation de ce pass est de permettre à l'interpréteur StableHLO d'évaluer les programmes contenant des opérations de remise à l'échelle TOSA. Détails

Évaluer les programmes quantifiés

L'interpréteur de référence StableHLO peut exécuter efficacement des programmes contenant des opérations quantifiées. Pour ce faire, il abaisse d'abord le programme à une représentation équivalente en utilisant uniquement des opérations sur les nombres entiers. Ce processus d'abaissement implique une série de passes de compilation qui transforment le programme avant l'interprétation.

Essentiellement, l'interpréteur utilise le pass stablehlo-legalize-quant-to-math pour convertir les opérations quantifiées en leurs implémentations arithmétiques entières correspondantes. Ce pass introduit des opérations de diffusion CHLO pour gérer la multiplication/division de la mise à l'échelle et l'addition du point zéro. Pour assurer la compatibilité avec l'interpréteur StableHLO, ces opérations CHLO sont ensuite légalisées en opérations StableHLO. Cela introduit des opérations liées à la forme qui sont ensuite canonisées et optimisées à l'aide d'une série de passes de canonisation.

La séquence complète des passes impliquées dans ce processus d'abaissement est la suivante :

stablehlo-legalize-quant-to-math
chlo-legalize-to-stablehlo
canonicalize
shape-legalize-to-stablehlo
stablehlo-canonicalize-dynamism

Scénarios de test quantifiés

StableHLO fournit une suite complète de cas de test quantifiés pour valider l'exactitude et le comportement des opérations quantifiées. Ces cas de test servent de tests unitaires et couvrent diverses opérations StableHLO dans des scénarios quantifiés.

Voici un exemple type de cas de test quantifié :

func.func @main() -> tensor<11xf32> {
    %operand_0 = stablehlo.constant dense<...> : tensor<11xf32>
    %operand_1 = stablehlo.constant dense<...> : tensor<11xf32>
    %golden = stablehlo.constant dense<...> : tensor<11xf32>

    %0 = stablehlo.uniform_quantize %operand_0 : (tensor<11xf32>) -> tensor<11x!quant.uniform<i8:f32, 0.3>>
    %1 = stablehlo.uniform_quantize %operand_1 : (tensor<11xf32>) -> tensor<11x!quant.uniform<i8:f32, 0.3>>

    %2 = stablehlo.add %1, %0 : tensor<11x!quant.uniform<i8:f32, 0.3>>

    %result = stablehlo.uniform_dequantize %2 : (tensor<11x!quant.uniform<i8:f32, 0.3>>) -> tensor<11xf32>

    %4 = stablehlo.custom_call @check.eq(%golden, %result) : (tensor<11xf32>, tensor<11xf32>) -> tensor<i1>

    return %3 : tensor<11xf32>
  }

et inclut :

  • Données d'entrée : valeurs d'entrée représentatives pour l'opération.
  • Sortie de référence : sortie attendue de l'opération lorsqu'elle est appliquée aux données d'entrée, conformément à l'interpréteur de référence StableHLO et à l'évaluateur HLO.

Ces cas de test sont utiles pour :

  • Valider la quantification StableHLO : s'assurer que le comportement de quantification des opérations StableHLO correspond aux résultats attendus.
  • Validation croisée : comparaison du comportement de la quantification StableHLO avec d'autres implémentations ou frameworks.
  • Débogage et développement : aide au développement et au débogage de nouvelles fonctionnalités ou optimisations de quantification.