Проблема с репликацией вывода квантованного вывода TF-Lite Conv2DPython

Программы на Python
Ответить
Anonymous
 Проблема с репликацией вывода квантованного вывода TF-Lite Conv2D

Сообщение Anonymous »

Я пытаюсь воспроизвести точный послойный вывод квантованной модели EfficientNet (модель TFLite, TensorFlow 2.17), повторно реализовав операции Conv2D, DepthwiseConv2D, FullyConnected, Add, Mul, Sub и Mean.
Все слои совпадают кроме Conv2D, где я постоянно вижу несоответствие -1 примерно для ~1% значений по сравнению с выходными данными TFLite во многих слоях (не во всех).
Выходные данные слоев получены из:
  • interpreter.invoke()
  • interpreter._get_ops_details()
  • interpreter.get_tensor(interpreter._get_tensor_details(_input, 0)["index"])
Пример несоответствия



Вход
Вывод SRM
Вывод DRM
tf-lite Выход




5907
12
13
13


-19651
-31
-31
-30


-12264
-46
-46
-46


  • SRM: режим одинарного округления
  • DRM: режим двойного округления
* Код для SRM и DRM приведен ниже.
Параметры квантования



Параметр
Значение




Iz (входная нулевая точка)
-127


Oz (выходная нулевая точка)
-3


Fz (нулевая точка фильтра)
0


bZ (нулевая точка смещения)
0


Is (входная шкала)
0.15857075154781342


Os (выходная шкала)
0.5111709833145142


Fs (шкала фильтра)
0.010584672912955284


bS (шкала смещения)
0.0016784195322543383


M (действительный множитель)
0,00328347971662879


M0 (квантованный множитель)
1805112064


shift
-8


смещение
-885



Реализация квантового умножения
Реализован tflite примитивы квантования:
  • SaturatingRoundingDoublingHighMul
  • RoundingDivideByPOT
  • Варианты с одинарным округлением (SRM) и двойным округлением (DRM)
(Полный код приведен ниже.)
Реализация Conv2D
Conv2D использует стандартную формулу свертки.
acc = sum((x - Iz) * (w - Fz)) + bias
acc = MultiplyByQuantizedMultiplier(acc, M[channel], shift[channel])
acc = acc + Oz
acc = clamp(acc, act_min, act_max)

(Полный код приведен ниже.)
Проблема
DepthwiseConv2D, который использует тот же код квантованного множителя, точно совпадает с tf-lite. Но Conv2D этого не делает, и я вижу расхождение во многих значениях примерно на 1 %.
Почему Conv2D не соответствует выводам TF-Lite, в то время как DepthwiseConv2D идеально соответствует?
Существует разница в точной реализации TFLite, но мы пока не смогли ее обнаружить. нам нужна помощь, чтобы разобраться в этом.
Коды
QuantizationFunction реализована для эмуляции умножения на квантованный множитель. Коды приведены ниже.
class QuantizationUtils:
INT32_MIN = -2**31
INT32_MAX = 2**31 - 1
bit = 31

@staticmethod
def quantize_multiplier_smaller_than_one(real_multiplier):
"""
Given a real multiplier in (0, 1) or > 1,
compute the quantized int32 multiplier and shift
that approximates real_multiplier ≈ multiplier * 2^shift.
"""

if real_multiplier == 0:
return 0, 0

shift = 0
significand = real_multiplier

# Normalize to [0.5, 1)
while significand < 0.5:
significand *= 2.0
shift -= 1
while significand >= 1.0:
significand /= 2.0
shift += 1

# Convert to fixed-point 32-bit representation (Q31 format)
q = int(round(significand * (1 31

# Saturate to int32 range
if result > QuantizationUtils.INT32_MAX:
result = QuantizationUtils.INT32_MAX
elif result < QuantizationUtils.INT32_MIN:
result = QuantizationUtils.INT32_MIN

return int(result)

@staticmethod
def rounding_divide_by_pot(x: int, exponent: int) -> int:
"""Equivalent to TFLite's RoundingDivideByPOT (round-to-nearest, ties away from zero)."""
assert 0 > exponent) + (1 if (remainder > threshold) else 0)

return result

@staticmethod
def multiply_by_quantized_multiplier_DRM(x: int, quantized_multiplier: int, shift: int) -> int:
"""
Simulates the core quantized multiply in TFLite:
result = x * quantized_multiplier * 2^shift, with rounding and saturation.
"""
# shift < 0 means right shift (division by power of two)
# shift > 0 means left shift (multiplication by power of two)
if shift < 0:
return QuantizationUtils.rounding_divide_by_pot(
QuantizationUtils.saturating_rounding_doubling_high_mul(x, quantized_multiplier), -shift
)
else:
return QuantizationUtils.saturating_rounding_doubling_high_mul(x, quantized_multiplier) * (1 int:
"""
Simulates the core quantized multiply in TFLite:
result = x * quantized_multiplier * 2^shift, with rounding and saturation.
"""
# shift < 0 means right shift (division by power of two)
# shift > 0 means left shift (multiplication by power of two)
# Perform 64-bit multiplication
a_64 = int(x)
b_64 = int(quantized_multiplier)
ab_64 = a_64 * b_64

if shift < 0:
return QuantizationUtils.rounding_divide_by_pot(
ab_64, 31-shift
)
else:
return QuantizationUtils.rounding_divide_by_pot(ab_64, 31+shift)

@staticmethod
def mul_by_quantized_multiplier_smaller_than_one_exp(x, M, rshift, impl="single"):
"""
Convenience wrapper; set impl="single" to do single rounding,
or impl="double" for double rounding.
"""
if impl == "single":
return QuantizationUtils.multiply_by_quantized_multiplier_SRM(x, M, rshift)
elif impl == "double":
return QuantizationUtils.multiply_by_quantized_multiplier_DRM(x, M, rshift)
else:
raise ValueError("impl must be 'single' or 'double'")

Код свертки (Conv2D):

def call(self, input):
"""
Performs the forward pass of the Conv2D layer

acc = sum((x - Iz) * (w - Fz)) + bias
acc = MultiplyByQuantizedMultiplier(acc, M[channel], shift[channel])
acc = acc + Oz
acc = clamp(acc, act_min, act_max)
"""
if self.verbose:
print("Running Conv2D Layer (TFLite compatible)\n--------------------")

# === 1. Pad and prepare input ===
input = self.input_padding(input)
_, self.H_in_pad, self.W_in_pad, _ = input.shape
self.input_shape_pad = (self.H_in_pad, self.W_in_pad)

# Output size calculation
self.output_shape = tuple(
((x - y) // z + 1)
for x, y, z in zip(self.input_shape_pad, self.filter_shape, self.strides)
)
self.H_out, self.W_out = self.output_shape

# === 2. Initialize output ===
final_output = np.zeros((self.batch, self.H_out, self.W_out, self.C_out_filter), dtype=np.int32)

# === 3. Perform convolution per output channel ===
for channel in range(self.C_out_filter):
# Extract per-channel filter and bias
filter_c = self.filter[:, :, :, channel]
bias_c = int(self.bias[channel])

# Per-channel multiplier & shift
M_c = self.M[channel]
shift_c = self.shift[channel]

for b in range(self.batch):
for i in range(self.H_out):
for j in range(self.W_out):
# Region slice
h_start = i * self.H_stride
w_start = j * self.W_stride
h_end = h_start + self.H_filter
w_end = w_start + self.W_filter

region = input[b, h_start:h_end, w_start:w_end, :]

# === 4. True TFLite accumulation ===
# Subtract input and weight zero-points before multiply
acc = np.sum((region - self.Iz) * (filter_c)) + bias_c

# === 5. Apply quantized scaling ===
acc = QuantizationUtils.mul_by_quantized_multiplier_smaller_than_one_exp(
acc, M_c, shift_c, impl="single"
)

# === 6. Add output zero-point and clamp ===
acc += self.Oz

# ReLU6 or ReLU (clamp to activation range)
if self.relu_flag:
acc = np.maximum(acc, self.Oz)
acc = np.clip(acc, self.act_min, self.act_max)

final_output[b, i, j, channel] = acc

return final_output.astype(np.int8)


Подробнее здесь: https://stackoverflow.com/questions/798 ... nce-output
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»