Majid Al-RaimiQuantization granularity: per-tensor vs per-channel scalesدقة التكميم (Granularity): مقاييس لكل موتر مقابل لكل قناة

COE 592Lecture 5.3المحاضرة 5.3Part 01الجزء 01

Quantization granularity: per-tensor vs per-channel scalesدقة التكميم (Granularity): مقاييس لكل موتر مقابل لكل قناة

Post-training quantization asks how to choose S and Z. The first knob is granularity: one scale for the whole weight tensor fails when output channels have wildly different ranges, so per-channel quantization gives each output channel its own scale and cuts reconstruction error, shown on a full 2-bit worked example.التكميم بعد التدريب (PTQ) يسأل كيف نختار S وZ. أول خيار هو الدقة (granularity): مقياس واحد لموتر الأوزان كله يفشل عندما تكون لقنوات المخرج مديات مختلفة جدًا، لذا يعطي التكميم لكل قناة (per-channel) كل قناة مخرج مقياسها الخاص ويقلل خطأ إعادة البناء، كما يظهر في مثال محلول كامل بـ2 بت.

Conceptsالمفاهيم
5
Slidesالشرائح
1-13
Readingمدة القراءة
30 min30 دقيقة
Understoodما فهمته
0/5 conceptsمفاهيم

Why this part mattersلماذا يهمّ هذا الجزء

The last two lectures gave you the machinery of linear quantization: a real number rr is stored as an integer qq with r≈S(q−Z)r \approx S(q - Z). What they did not settle is how to choose SS and ZZ for a real network that has already been trained. This part starts that story with the first and most exam-visible decision: how many weights should share one scale.

The ideas come in a chain. First, the framing: post-training quantization and its three questions. Second, the menu of granularities. Third, why the simplest choice, one scale per tensor, breaks on small models. Fourth and fifth, the lecture’s 2-bit example computed by hand under both schemes, ending with the reason per-channel scales live on output channels. Expect the midterm to hand you a small matrix and ask for SS, qq and the error under both schemes, so the worked examples are written to be copied step by step.

أعطتك المحاضرتان السابقتان أدوات التكميم الخطي: يُخزَّن العدد الحقيقي rr كعدد صحيح qq بحيث r≈S(q−Z)r \approx S(q - Z). لكنهما لم تحسما كيف نختار SS وZZ لشبكة حقيقية دُرِّبت بالفعل. يبدأ هذا الجزء تلك القصة بالقرار الأول والأكثر ظهورًا في الامتحان: كم وزنًا يجب أن يتشارك scale واحدًا؟

تأتي الأفكار في سلسلة. أولًا الإطار العام: التكميم بعد التدريب (post-training quantization) وأسئلته الثلاثة. ثانيًا قائمة مستويات الدقة (granularity). ثالثًا لماذا ينهار أبسط خيار، أي scale واحد لكل tensor، في النماذج الصغيرة. رابعًا وخامسًا مثال المحاضرة ذو 2 bit محسوبًا باليد بالطريقتين، وننتهي بسبب وضع مقاييس per-channel على قنوات الخرج (output channels). توقّع أن يعطيك الامتحان النصفي مصفوفة صغيرة ويطلب SS وqq والخطأ بالطريقتين، لذلك كُتبت الأمثلة المحلولة بحيث تنسخها خطوة بخطوة.

By the end you canفي نهاية هذا الجزء ستستطيع

  1. State what post-training quantization does and name the three questions it answers about S and Z.أن تذكر ما يفعله التكميم بعد التدريب (PTQ) وتسمّي الأسئلة الثلاثة التي يجيب عنها بشأن S وZ.
  2. Distinguish per-tensor, per-channel and group granularity by how many weights share one scale.أن تميّز بين granularity من نوع per-tensor وper-channel وgroup بحسب عدد الأوزان التي تتشارك scale واحدًا.
  3. Compute S, q and the reconstruction error for a small weight matrix under symmetric per-tensor and per-channel quantization.أن تحسب S وq وخطأ إعادة البناء لمصفوفة أوزان صغيرة في التكميم المتماثل per-tensor وper-channel.
  4. Explain why one outlier output channel ruins per-tensor quantization, citing the MobileNetV2 evidence.أن تشرح لماذا تُفسد قناة خرج شاذة واحدة تكميم per-tensor، مستشهدًا بدليل MobileNetV2.
  5. Explain why per-channel scales are taken over output channels and not input channels.أن تشرح لماذا تؤخذ مقاييس per-channel على قنوات الخرج (output channels) وليس على قنوات الدخل.

Suppose a colleague hands you a ResNet trained in FP32 and says: make it run in INT8 on our microcontroller by Friday, and no, you cannot retrain it. You have the weights, perhaps a few hundred unlabeled images, and that is all.

لنفترض أن زميلًا أعطاك شبكة ResNet مدرّبة بدقة FP32 وقال: اجعلها تعمل بـ INT8 على المتحكم الدقيق (microcontroller) لدينا قبل يوم الجمعة، ولا، لا يمكنك إعادة تدريبها. لديك الأوزان، وربما بضع مئات من الصور غير الموسومة، وهذا كل شيء.

That job is Post-training quantization (PTQ). The network’s weights are frozen; the only things you get to choose are the quantization parameters, the scale SS and the zero point ZZ, for every tensor you quantize. Its opposite, which the lecture covers later, is Quantization-aware training (QAT), where quantization is simulated during training so the weights can adapt to it. PTQ is cheap and needs no labels; QAT costs a training run but recovers more accuracy at low bit widths.

هذه المهمة هي post-training quantization (التكميم بعد التدريب). أوزان الشبكة مجمّدة، والشيء الوحيد الذي تختاره هو معاملات التكميم: الـ scale SS ونقطة الصفر (zero point) ZZ، لكل tensor تكمّمه. ونقيضه، الذي تغطيه المحاضرة لاحقًا، هو quantization-aware training (التدريب الواعي بالتكميم)، حيث يُحاكى التكميم أثناء التدريب كي تتكيّف الأوزان معه. الـ PTQ رخيص ولا يحتاج إلى تسميات (labels)، أما QAT فيكلّف عملية تدريب كاملة لكنه يستعيد دقة أكبر عند عدد بتات منخفض.

The lecture breaks the PTQ question “what are the best (S,Z)(S, Z)?” into three independent sub-questions, and the rest of the lecture’s first half answers them in order.

تقسّم المحاضرة سؤال الـ PTQ «ما أفضل (S,Z)(S, Z)؟» إلى ثلاثة أسئلة فرعية مستقلة، ويجيب باقي النصف الأول من المحاضرة عنها بالترتيب.

Granularityالدقة الحبيبية (Granularity)
Topic I, this partالموضوع الأول، هذا الجزء

How many weights share one S? The whole tensor, one channel, or a small group.

كم وزنًا يتشارك S واحدًا؟ الـ tensor بأكمله، أو قناة واحدة، أو مجموعة صغيرة.

thenثم
Range clippingقص المدى
Topic IIالموضوع الثاني

Which |r|_max to map onto q_max? The observed max, or a smaller clipped value.

أي قيمة |r|_max نربطها بـ q_max؟ القيمة العظمى المرصودة، أو قيمة أصغر بعد القص.

thenثم
Roundingالتقريب
Topic IIIالموضوع الثالث

Round each value to the nearest level, or choose up or down to cut layer error.

قرّب كل قيمة إلى أقرب مستوى، أو اختر التقريب للأعلى أو للأسفل لتقليل خطأ الطبقة.

PTQ splits the choice of (S, Z) into three questionsيقسّم PTQ اختيار (S, Z) إلى ثلاثة أسئلة

Keep the three apart in your head. Granularity decides who shares a scale. Dynamic range clipping decides how large that scale is, given the values that share it. Rounding decides which level each value lands on once the grid is fixed; the default, Rounding to nearest, turns out not to be optimal. Every one of the three changes the same final number, the error between the original and the dequantized weights, so they interact, but each can be studied with the other two held fixed.

أبقِ الثلاثة منفصلة في ذهنك. الـ granularity تحدد من يتشارك الـ scale. وdynamic range clipping (قصّ المدى الديناميكي) يحدد كم يكون كبيرًا هذا الـ scale، بالنظر إلى القيم التي تتشاركه. والتقريب (rounding) يحدد أي مستوى تقع عليه كل قيمة بعد تثبيت الشبكة؛ والخيار الافتراضي، rounding to nearest (التقريب إلى الأقرب)، يتبيّن أنه ليس الأمثل. كل واحد من الثلاثة يغيّر الرقم النهائي نفسه، أي الخطأ بين الأوزان الأصلية والأوزان بعد إزالة التكميم (dequantized)، لذا فهي تتفاعل فيما بينها، لكن يمكن دراسة كل واحد منها مع تثبيت الاثنين الآخرين.

Recall

Name the three PTQ sub-questions and say in one phrase what each one decides.سمِّ الأسئلة الفرعية الثلاثة في PTQ وقل بعبارة واحدة ماذا يحدد كل منها.

Granularity: which weights share one scale. Dynamic range clipping: what range is mapped onto the integer grid, which sets the size of S. Rounding: which grid level each value is assigned to once S is fixed.

الـ granularity: أي الأوزان تتشارك scale واحدًا. الـ dynamic range clipping: أي مدى يُسقَط على الشبكة الصحيحة، وهذا يحدد حجم S. الـ rounding: أي مستوى في الشبكة تُسنَد إليه كل قيمة بعد تثبيت S.

Picture a convolution weight tensor with 64 output channels, 32 input channels and 3 × 3 kernels: 18,432 numbers. You could give all of them one scale, give each of the 64 filters its own scale, or cut each filter into blocks of 16 and give every block a scale.

تخيّل tensor أوزان لطبقة التفاف (convolution) فيه 64 قناة خرج و32 قناة دخل وkernels بحجم 3 × 3: أي 18,432 رقمًا. يمكنك أن تعطيها كلها scale واحدًا، أو أن تعطي كل واحد من المرشحات (filters) الـ 64 الـ scale الخاص به، أو أن تقطع كل مرشح إلى كتل من 16 وتعطي كل كتلة scale.

That choice is Quantization granularity: how many elements share one scale factor. The lecture lists three levels, from coarse to fine.

هذا الاختيار هو quantization granularity (دقة التكميم): كم عنصرًا يتشارك معامل scale واحدًا. تذكر المحاضرة ثلاثة مستويات، من الخشن إلى الدقيق.

Coarse to fine: one scale for the tensor, one per output channel row, one per group of fourمن الخشن إلى الدقيق: scale واحد للـ tensor كله، وواحد لكل صف قناة خرج، وواحد لكل مجموعة من أربعة

The trade-off runs in one direction. Finer granularity means each scale only has to fit the values it actually covers, so the step size SS shrinks and the rounding error with it. The price is more scales to store and, more importantly, more scales the hardware has to apply. The Qualcomm white paper on quantization (Nagel et al., 2021) puts it plainly: per-tensor is the most common choice because all accumulators use the same scale factor, finer groups improve accuracy at the cost of overhead, and many fixed-point accelerators do not support that extra logic.

المفاضلة تسير في اتجاه واحد. الدقة الأنعم تعني أن كل scale عليه أن يلائم فقط القيم التي يغطيها فعلًا، فيصغر حجم الخطوة SS ويصغر معه خطأ التقريب. الثمن هو مزيد من قيم الـ scale التي يجب تخزينها، والأهم من ذلك مزيد من قيم الـ scale التي يجب على العتاد تطبيقها. الورقة البيضاء من Qualcomm عن التكميم (Nagel et al., 2021) تقولها بوضوح: الـ per-tensor هو الخيار الأكثر شيوعًا لأن كل المراكمات (accumulators) تستخدم معامل scale نفسه، والمجموعات الأدق تحسّن الدقة على حساب كلفة إضافية، وكثير من مسرّعات الفاصلة الثابتة (fixed-point accelerators) لا تدعم هذا المنطق الإضافي.

Granularityمستوى الدقة (granularity)Values sharing one scaleالقيم التي تتشارك scale واحدًاNumber of scalesعدد قيم الـ scaleStrengthنقطة القوةWeaknessنقطة الضعف
Per-tensorPer-tensorThe whole weight tensorالـ tensor الأوزان كله11Simplest; supported by every fixed-point acceleratorالأبسط، ويدعمه كل مسرّع فاصلة ثابتة (fixed-point accelerator)One outlier sets the step size for everyoneقيمة شاذة واحدة تحدد حجم الخطوة للجميع
Per-channelPer-channelOne output channel (one row of W, one filter)قناة خرج واحدة (صف واحد من W، أي مرشح واحد)C_outC_outStandard for weights in modern toolchains (TFLite per-axis, PyTorch)المعيار للأوزان في أدوات اليوم (TFLite per-axis، وPyTorch)Still one scale for a long row; hard to apply to activationsما زال scale واحدًا لصف طويل، ويصعب تطبيقه على التنشيطات (activations)
GroupGroupA small block inside a channel, for example 16 or 32 elementsكتلة صغيرة داخل القناة، مثلًا 16 أو 32 عنصرًا(total weights) / G(total weights) / GBest accuracy at 4 bits and belowأفضل دقة عند 4 bits فأقلMore scale storage and accumulator logic (part 2)تخزين أكثر للـ scales ومنطق مراكمة أكثر (الجزء 2)
The three granularities on the lecture’s menuمستويات الدقة الثلاثة في قائمة المحاضرة

Recall

A Conv2d layer has 128 output channels, 64 input channels and 3 x 3 kernels. How many weight scales does per-tensor and per-channel quantization need?طبقة Conv2d فيها 128 قناة خرج و64 قناة دخل وkernels بحجم 3 x 3. كم scale للأوزان يحتاج تكميم per-tensor وتكميم per-channel؟

Per-tensor: 1. Per-channel: 128, one per output channel, each covering 64 × 3 × 3 = 576 weights. The input channel count does not change the number of per-channel scales.

الـ per-tensor: 1. الـ per-channel: 128، واحد لكل قناة خرج، يغطي كل منها 64 × 3 × 3 = 576 وزنًا. عدد قنوات الدخل لا يغيّر عدد مقاييس الـ per-channel.

Take two filters in the same layer. One has weights in [-128, 128], the other in (-0.5, 0.5). Quantize both to INT8 with one shared scale: S = 128 / 127 ≈ 1.01. Every weight of the second filter divided by 1.01 is below 0.5 in magnitude, so all of them round to 0. That filter is erased, even with 8 bits.

خذ مرشحين في الطبقة نفسها. أحدهما أوزانه في [-128, 128]، والآخر في (-0.5, 0.5). كمّم الاثنين إلى INT8 بـ scale مشترك واحد: S = 128 / 127 ≈ 1.01. كل وزن من المرشح الثاني بعد قسمته على 1.01 تكون قيمته المطلقة أقل من 0.5، فتُقرَّب كلها إلى 0. هذا المرشح يُمحى تمامًا، حتى مع 8 bits.

This is the example Nagel et al. (ICCV 2019) use, and it is the whole reason this part exists. Before going further, fix the recipe the lecture uses for weights. Weights are roughly centred on zero, so the lecture uses Symmetric quantization: the zero point is Z=0Z = 0 and the range is set by the largest magnitude.

هذا هو المثال الذي يستخدمه Nagel et al. (ICCV 2019)، وهو السبب الذي يوجد هذا الجزء من أجله أصلًا. قبل المتابعة، ثبّت الوصفة التي تستخدمها المحاضرة للأوزان. الأوزان متمركزة تقريبًا حول الصفر، لذا تستخدم المحاضرة symmetric quantization (التكميم المتماثل): نقطة الصفر Z=0Z = 0 والمدى تحدده أكبر قيمة مطلقة.

∣r∣max⁡=∣W∣max⁡,S=∣r∣max⁡qmax⁡,qmax⁡=2N−1−1|r|_{\max} = |\mathbf{W}|_{\max}, \qquad S = \dfrac{|r|_{\max}}{q_{\max}}, \qquad q_{\max} = 2^{N-1} - 1
Symmetric scale for N-bit weightsالمقياس المتماثل لأوزان من N بت
q=clip⁡ ⁣(round⁡ ⁣(WS), −qmax⁡, qmax⁡),W^=S qq = \operatorname{clip}\!\left(\operatorname{round}\!\left(\dfrac{W}{S}\right),\ -q_{\max},\ q_{\max}\right), \qquad \hat{W} = S\,q
Quantize, then reconstruct (dequantize)كمِّم، ثم أعد البناء (إلغاء التكميم dequantize)

Notice what qmax⁡=2N−1−1q_{\max} = 2^{N-1} - 1 does to the integer range. An N-bit two’s complement integer can hold -2^(N-1) up to 2^(N-1) - 1, but a symmetric grid uses only -q_max to +q_max, so the most negative code is never produced. For N = 8 that is [-127, 127], exactly the range the TensorFlow Lite (LiteRT) specification mandates for weights. For N = 2 it is dramatic: q_max = 2^1 - 1 = 1, so only the three codes -1, 0, 1 are used, and the fourth code, -2, is wasted.

لاحظ ما تفعله qmax⁡=2N−1−1q_{\max} = 2^{N-1} - 1 بمدى الأعداد الصحيحة. العدد الصحيح ذو N bit بتمثيل المتمم الثنائي (two’s complement) يمكن أن يحمل القيم من -2^(N-1) حتى 2^(N-1) - 1، لكن الشبكة المتماثلة لا تستخدم إلا من -q_max إلى +q_max، فلا يُنتَج الرمز الأكثر سالبية أبدًا. عند N = 8 يكون ذلك [-127, 127]، وهو بالضبط المدى الذي تفرضه مواصفة TensorFlow Lite (LiteRT) للأوزان. وعند N = 2 يكون الأثر كبيرًا: q_max = 2^1 - 1 = 1، فلا تُستخدم إلا الرموز الثلاثة -1, 0, 1، ويضيع الرمز الرابع -2.

One scale, many channels: the outlier problemscale واحد وقنوات كثيرة: مشكلة القيم الشاذة (outliers)

With one scale per tensor, ∣r∣max⁡|r|_{\max} is the largest weight in the entire layer. If one output channel holds that outlier, its range dictates the step size for every channel. Channels whose weights are much smaller collapse onto a handful of levels, often just zero.

مع scale واحد لكل tensor، تكون ∣r∣max⁡|r|_{\max} أكبر وزن في الطبقة كلها. إذا كانت قناة خرج واحدة تحمل هذه القيمة الشاذة، فإن مداها يفرض حجم الخطوة على كل القنوات. القنوات ذات الأوزان الأصغر بكثير تنهار إلى عدد قليل من المستويات، وغالبًا إلى الصفر وحده.

A shared grid sized for the outlier channel oc3 swallows the small channels; per-channel scales fit each oneشبكة مشتركة مضبوطة على قناة القيم الشاذة oc3 تبتلع القنوات الصغيرة، بينما تلائم مقاييس per-channel كل قناة على حدة

The lecture’s evidence is the box plot from Nagel et al.: the per-output-channel weight ranges of the first depthwise-separable layer in MobileNetV2. Most of the 32 channels sit within a few units of zero, while a few stretch to around ±50 to ±100. The lecture’s summary:

دليل المحاضرة هو مخطط الصندوق (box plot) من Nagel et al.: مديات الأوزان لكل قناة خرج في أول طبقة depthwise-separable في MobileNetV2. معظم القنوات الـ 32 تقع في حدود وحدات قليلة حول الصفر، بينما يمتد بعضها إلى نحو ±50 حتى ±100. خلاصة المحاضرة:

  • Per-tensor quantization works well for large models but accuracy drops for small models.تكميم per-tensor يعمل جيدًا للنماذج الكبيرة لكن الدقة تنخفض في النماذج الصغيرة.
  • The common failure is a large difference, more than 100×, in weight ranges across output channels, caused by outlier weights.الفشل الشائع هو فرق كبير، أكثر من 100×، في مديات الأوزان بين قنوات الخرج، تسببه الأوزان الشاذة.
  • The solution is per-channel quantization.الحل هو تكميم per-channel.

The numbers in the paper are stark. MobileNetV2 reaches 71.72% top-1 on ImageNet in FP32. With plain per-tensor INT8 it drops to 0.12%, essentially random guessing; with per-channel INT8 it keeps 70.65%. Depthwise layers are the worst offenders because each output channel has only a few weights (3 × 3 = 9), so one channel can drift far from the others, and folding batch normalization into the weights amplifies the imbalance (Nagel et al., 2021). Large models like ResNets have many weights per channel and smoother ranges, which is why per-tensor survives there.

الأرقام في الورقة صارخة. يصل MobileNetV2 إلى دقة top-1 قدرها 71.72% على ImageNet بدقة FP32. ومع تكميم INT8 عادي من نوع per-tensor تهبط إلى 0.12%، أي ما يعادل التخمين العشوائي؛ ومع INT8 من نوع per-channel تحافظ على 70.65%. طبقات الـ depthwise هي أسوأ المتسببين لأن كل قناة خرج فيها لا تملك إلا أوزانًا قليلة (3 × 3 = 9)، فيمكن لقناة واحدة أن تبتعد كثيرًا عن الأخرى، كما أن دمج الـ batch normalization في الأوزان يضخّم هذا التفاوت (Nagel et al., 2021). أما النماذج الكبيرة مثل ResNets فلديها أوزان كثيرة في كل قناة ومديات أكثر انتظامًا، ولهذا ينجو الـ per-tensor هناك.

Quick checkاختبار سريع

In the lecture's 2-bit symmetric scheme, which integer codes can a quantized weight take?في طريقة المحاضرة المتماثلة بـ 2 bit، ما الرموز الصحيحة التي يمكن أن يأخذها وزن مكمَّم؟

Now the lecture’s example, done by hand. Rows are output channels (oc), columns are input channels (ic), and we quantize to N = 2 bits with one scale.

الآن مثال المحاضرة، محلولًا باليد. الصفوف هي قنوات الخرج (oc)، والأعمدة هي قنوات الدخل (ic)، ونكمّم إلى N = 2 bits بـ scale واحد.

W=[2.09−0.981.480.090.05−0.14−1.082.12−0.911.920−1.031.8701.531.49]\mathbf{W} = \begin{bmatrix} 2.09 & -0.98 & 1.48 & 0.09 \\ 0.05 & -0.14 & -1.08 & 2.12 \\ -0.91 & 1.92 & 0 & -1.03 \\ 1.87 & 0 & 1.53 & 1.49 \end{bmatrix}
The weight matrix: rows = output channels oc0 to oc3, columns = input channelsمصفوفة الأوزان: الصفوف = قنوات الإخراج من oc0 إلى oc3، والأعمدة = قنوات الإدخال

Worked exampleمثال محلول

Per-tensor, 2-bit symmetricPer-tensor، تكميم متماثل بـ 2 bit

  1. Find the rangeأوجد المدى

    The largest magnitude anywhere in W is 2.12 (row oc1, last column), so |r|_max = 2.12.
    أكبر قيمة مطلقة في أي مكان من W هي 2.12 (الصف oc1، العمود الأخير)، إذن |r|_max = 2.12.
  2. Compute the scaleاحسب الـ scale

    S=∣r∣max⁡qmax⁡=2.1222−1−1=2.121=2.12S = \dfrac{|r|_{\max}}{q_{\max}} = \dfrac{2.12}{2^{2-1} - 1} = \dfrac{2.12}{1} = 2.12
    S=∣r∣max⁡qmax⁡=2.1222−1−1=2.121=2.12S = \dfrac{|r|_{\max}}{q_{\max}} = \dfrac{2.12}{2^{2-1} - 1} = \dfrac{2.12}{1} = 2.12
  3. Divide and roundاقسم وقرّب

    Divide every weight by 2.12 and round to the nearest integer in [-1, 1]. Anything with |W| < 1.06 (half a step) becomes 0. So 2.09 / 2.12 = 0.986 → 1, 1.48 / 2.12 = 0.698 → 1, -0.98 / 2.12 = -0.462 → 0, -1.08 / 2.12 = -0.509 → -1 (just past half a step), and -1.03 / 2.12 = -0.486 → 0 (just short of it).
    q=[101000−1101001011]\mathbf{q} = \begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 0 & -1 & 1 \\ 0 & 1 & 0 & 0 \\ 1 & 0 & 1 & 1 \end{bmatrix}
    اقسم كل وزن على 2.12 وقرّبه إلى أقرب عدد صحيح في [-1, 1]. أي وزن فيه |W| < 1.06 (نصف خطوة) يصبح 0. لذلك 2.09 / 2.12 = 0.986 → 1، و1.48 / 2.12 = 0.698 → 1، و-0.98 / 2.12 = -0.462 → 0، و-1.08 / 2.12 = -0.509 → -1 (تجاوز نصف الخطوة بقليل)، و-1.03 / 2.12 = -0.486 → 0 (قصُر عنها بقليل).
    q=[101000−1101001011]\mathbf{q} = \begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 0 & -1 & 1 \\ 0 & 1 & 0 & 0 \\ 1 & 0 & 1 & 1 \end{bmatrix}
  4. Reconstructأعد البناء

    Multiply back by S: every nonzero code becomes ±2.12.
    Sq=[2.1202.12000−2.122.1202.12002.1202.122.12]S\mathbf{q} = \begin{bmatrix} 2.12 & 0 & 2.12 & 0 \\ 0 & 0 & -2.12 & 2.12 \\ 0 & 2.12 & 0 & 0 \\ 2.12 & 0 & 2.12 & 2.12 \end{bmatrix}
    اضرب مرة أخرى في S: كل رمز غير صفري يصبح ±2.12.
    Sq=[2.1202.12000−2.122.1202.12002.1202.122.12]S\mathbf{q} = \begin{bmatrix} 2.12 & 0 & 2.12 & 0 \\ 0 & 0 & -2.12 & 2.12 \\ 0 & 2.12 & 0 & 0 \\ 2.12 & 0 & 2.12 & 2.12 \end{bmatrix}
  5. Measure the errorقِس الخطأ

    Sum the squared differences over all 16 entries, then take the square root (the Frobenius norm). The biggest contributors are the weights zeroed out (-0.98, -0.91, -1.03) and the small positives pushed up to 2.12 (1.48, 1.53, 1.49).
    ∥W−Sq∥F=∑i,j(Wij−Sqij)2=5.219=2.28\lVert \mathbf{W} - S\mathbf{q} \rVert_F = \sqrt{\textstyle\sum_{i,j} (W_{ij} - S q_{ij})^2} = \sqrt{5.219} = 2.28
    اجمع مربعات الفروق على المدخلات الـ 16 كلها، ثم خذ الجذر التربيعي (معيار فروبينيوس، Frobenius norm). أكبر المساهمين هم الأوزان التي صُفّرت (-0.98 و-0.91 و-1.03) والقيم الموجبة الصغيرة التي دُفعت إلى 2.12 (1.48 و1.53 و1.49).
    ∥W−Sq∥F=∑i,j(Wij−Sqij)2=5.219=2.28\lVert \mathbf{W} - S\mathbf{q} \rVert_F = \sqrt{\textstyle\sum_{i,j} (W_{ij} - S q_{ij})^2} = \sqrt{5.219} = 2.28
  6. Resultالنتيجة

    S = 2.12, codes only in {-1, 0, 1}, error ‖W - Sq‖_F = 2.28.
    S = 2.12، والرموز فقط في {-1, 0, 1}، والخطأ ‖W - Sq‖_F = 2.28.

Recall

Without looking back: what is S for this matrix under 2-bit per-tensor quantization, and why is it equal to |r|_max?دون النظر إلى ما سبق: ما قيمة S لهذه المصفوفة في تكميم per-tensor بـ 2 bit، ولماذا تساوي |r|_max؟

S = 2.12. Because q_max = 2^(2-1) - 1 = 1, dividing |r|_max by 1 leaves it unchanged. At 8 bits the same matrix would give S = 2.12 / 127 ≈ 0.0167.

S = 2.12. لأن q_max = 2^(2-1) - 1 = 1، فقسمة |r|_max على 1 تتركها كما هي. وعند 8 bits تعطي المصفوفة نفسها S = 2.12 / 127 ≈ 0.0167.

Quick checkاختبار سريع

Under 2-bit per-tensor quantization with S = 2.12, which code does the weight -1.03 receive?في تكميم per-tensor بـ 2 bit مع S = 2.12، ما الرمز الذي يأخذه الوزن -1.03؟

Same matrix, same 2 bits, but now each output channel (each row) gets its own scale. With q_max = 1, each scale is simply that row’s largest magnitude.

المصفوفة نفسها، و2 bits نفسها، لكن الآن تحصل كل قناة خرج (كل صف) على scale خاص بها. ومع q_max = 1، يكون كل scale ببساطة أكبر قيمة مطلقة في ذلك الصف.

Si=max⁡j∣Wij∣qmax⁡,qij=clip⁡ ⁣(round⁡ ⁣(WijSi),−qmax⁡,qmax⁡)S_i = \dfrac{\max_j |W_{ij}|}{q_{\max}}, \qquad q_{ij} = \operatorname{clip}\!\left(\operatorname{round}\!\left(\dfrac{W_{ij}}{S_i}\right), -q_{\max}, q_{\max}\right)
Per-channel symmetric quantization: one scale per output channel iالتكميم المتماثل لكل قناة: مقياس واحد لكل قناة إخراج i

Worked exampleمثال محلول

Per-channel, 2-bit symmetricPer-channel، تكميم متماثل بـ 2 bit

  1. One range per rowمدى واحد لكل صف

    oc0: |r|_max = 2.09, so S_0 = 2.09. oc1: 2.12, so S_1 = 2.12. oc2: 1.92, so S_2 = 1.92. oc3: 1.87, so S_3 = 1.87. Only oc1 keeps the per-tensor scale, because it is the row that holds the global maximum.
    oc0: |r|_max = 2.09، إذن S_0 = 2.09. oc1: 2.12، إذن S_1 = 2.12. oc2: 1.92، إذن S_2 = 1.92. oc3: 1.87، إذن S_3 = 1.87. وحده oc1 يحتفظ بالـ scale نفسه الذي في per-tensor، لأنه الصف الذي يحمل القيمة العظمى الكلية.
  2. Divide each row by its own scaleاقسم كل صف على الـ scale الخاص به

    Most codes do not change. The one that does is in oc2: -1.03 / 1.92 = -0.536 → -1, where per-tensor gave -1.03 / 2.12 = -0.486 → 0. Shrinking the step from 2.12 to 1.92 pushed it just past half a step.
    q=[101000−11010−11011]\mathbf{q} = \begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 0 & -1 & 1 \\ 0 & 1 & 0 & \mathbf{-1} \\ 1 & 0 & 1 & 1 \end{bmatrix}
    معظم الرموز لا تتغير. الرمز الذي يتغير موجود في oc2: -1.03 / 1.92 = -0.536 → -1، بينما أعطى per-tensor القيمة -1.03 / 2.12 = -0.486 → 0. تصغير الخطوة من 2.12 إلى 1.92 دفعه إلى ما بعد نصف الخطوة بقليل.
    q=[101000−11010−11011]\mathbf{q} = \begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 0 & -1 & 1 \\ 0 & 1 & 0 & \mathbf{-1} \\ 1 & 0 & 1 & 1 \end{bmatrix}
  3. Reconstruct row by rowأعد البناء صفًا صفًا

    Each row is multiplied by its own scale, written S⊙q\mathbf{S} \odot \mathbf{q}.
    S⊙q=[2.0902.09000−2.122.1201.920−1.921.8701.871.87]\mathbf{S} \odot \mathbf{q} = \begin{bmatrix} 2.09 & 0 & 2.09 & 0 \\ 0 & 0 & -2.12 & 2.12 \\ 0 & 1.92 & 0 & -1.92 \\ 1.87 & 0 & 1.87 & 1.87 \end{bmatrix}
    يُضرب كل صف في الـ scale الخاص به، ونكتب ذلك S⊙q\mathbf{S} \odot \mathbf{q}.
    S⊙q=[2.0902.09000−2.122.1201.920−1.921.8701.871.87]\mathbf{S} \odot \mathbf{q} = \begin{bmatrix} 2.09 & 0 & 2.09 & 0 \\ 0 & 0 & -2.12 & 2.12 \\ 0 & 1.92 & 0 & -1.92 \\ 1.87 & 0 & 1.87 & 1.87 \end{bmatrix}
  4. Measure the errorقِس الخطأ

    ∥W−S⊙q∥F=4.32=2.08  <  2.28\lVert \mathbf{W} - \mathbf{S} \odot \mathbf{q} \rVert_F = \sqrt{4.32} = 2.08 \;<\; 2.28
    ∥W−S⊙q∥F=4.32=2.08  <  2.28\lVert \mathbf{W} - \mathbf{S} \odot \mathbf{q} \rVert_F = \sqrt{4.32} = 2.08 \;<\; 2.28
  5. Resultالنتيجة

    Per-channel error 2.08 is below per-tensor 2.28, with one code changed.
    خطأ per-channel البالغ 2.08 أقل من خطأ per-tensor البالغ 2.28، مع تغيّر رمز واحد فقط.

Where did the improvement come from? Not only from the one flipped code. Break the squared error down by row:

من أين جاء التحسّن؟ ليس فقط من الرمز الواحد الذي انقلب. فكّك الخطأ المربّع حسب الصف:

ChannelالقناةPer-tensor SS في per-tensorPer-tensor squared errorالخطأ المربّع في per-tensorPer-channel S_iS_i في per-channelPer-channel squared errorالخطأ المربّع في per-channel
oc0oc02.122.121.3791.3792.092.091.3411.341
oc1oc12.122.121.1041.1042.122.121.1041.104
oc2oc22.122.121.9291.9291.921.921.6201.620
oc3oc32.122.120.8080.8081.871.870.2600.260
Totalالمجموع5.219 → √ = 2.285.219 → √ = 2.284.32 → √ = 2.084.32 → √ = 2.08
Squared error per output channel (sum over the row of (W - reconstruction)^2)الخطأ المربّع لكل قناة خرج (مجموع (W - إعادة البناء)^2 على الصف)

oc3 gains the most, and none of its codes changed: its nonzero weights 1.87, 1.53, 1.49 are now reconstructed as 1.87 instead of 2.12, much closer. oc2 gains from the flipped code (error on that entry falls from 1.03 to |-1.03 + 1.92| = 0.89) and from 1.92 now being exact. oc1 is unchanged because its scale is unchanged. The lesson: a smaller, better-fitted scale helps both by moving values onto nonzero codes and by placing the levels closer to the values.

oc3 يستفيد أكثر من غيره، ولم يتغير أي من رموزه: أوزانه غير الصفرية 1.87, 1.53, 1.49 يُعاد بناؤها الآن كـ 1.87 بدل 2.12، وهذا أقرب بكثير. ويستفيد oc2 من الرمز المنقلب (ينخفض الخطأ في ذلك المدخل من 1.03 إلى |-1.03 + 1.92| = 0.89) ومن أن 1.92 أصبحت الآن دقيقة تمامًا. أما oc1 فلا يتغير لأن الـ scale الخاص به لم يتغير. الدرس: الـ scale الأصغر والأكثر ملاءمة يساعد بطريقتين: بنقل القيم إلى رموز غير صفرية، وبوضع المستويات أقرب إلى القيم.

Recall

Compute -0.91 / 1.92 and round it. Does the first weight of oc2 change code under per-channel quantization?احسب -0.91 / 1.92 وقرّبه. هل يتغير رمز الوزن الأول في oc2 في تكميم per-channel؟

-0.91 / 1.92 = -0.474, which rounds to 0, the same as per-tensor (-0.91 / 2.12 = -0.429 → 0). A near miss: it would need |W| ≥ 0.96 to reach code -1. So in oc2 only -1.03 flips.

-0.91 / 1.92 = -0.474، ويُقرَّب إلى 0، تمامًا كما في per-tensor (-0.91 / 2.12 = -0.429 → 0). كاد أن يتغير: كان سيحتاج إلى |W| ≥ 0.96 ليصل إلى الرمز -1. لذلك في oc2 لا ينقلب إلا -1.03.

Try it live. Switch between the two schemes and watch the highlighted cell; then change the bit width. At 3 and 4 bits per-channel is actually a hair worse on this particular matrix (0.710 versus 0.699 at 3 bits), because its row ranges are almost equal (1.87 to 2.12) and which side of a rounding boundary each value lands on is partly luck. Per-channel wins reliably when ranges differ a lot, which is exactly the MobileNetV2 situation.

جرّبه مباشرة. بدّل بين الطريقتين وراقب الخلية المظلَّلة، ثم غيّر عدد البتات. عند 3 و4 bits يكون per-channel في الواقع أسوأ بفارق ضئيل على هذه المصفوفة بالذات (0.710 مقابل 0.699 عند 3 bits)، لأن مديات صفوفها متساوية تقريبًا (من 1.87 إلى 2.12)، وفي أي جانب من حد التقريب تقع كل قيمة هو جزئيًا مسألة حظ. يفوز per-channel بشكل موثوق عندما تختلف المديات كثيرًا، وهذا بالضبط وضع MobileNetV2.

SimulatorمحاكيSame 4x4 weights, one scale or fourأوزان 4x4 نفسها، بمقياس واحد أو أربعة
W (rows = oc)
2.09
-0.98
1.48
0.09
0.05
-0.14
-1.08
2.12
-0.91
1.92
0
-1.03
1.87
0
1.53
1.49
q = clip(round(W/S))
1
0
1
0
0
0
-1
1
0
1
0
0
1
0
1
1
S times q
2.12
0
2.12
0
0
0
-2.12
2.12
0
2.12
0
0
2.12
0
2.12
2.12
S0 = 2.12S1 = 2.12S2 = 2.12S3 = 2.12
q range-1 to 1codesrestricted symmetric, q_max = 2^(N-1) - 1
per-tensor error2.285||W - Sq||_Fsquared: 5.219
per-channel error2.080||W - S⊙q||_F1 code(s) differ, highlighted

Why output channels, not input channelsلماذا قنوات الخرج وليس قنوات الدخل

The slides draw the scale arrows along rows, the output channels, and the convolution picture boxes the filter dimension c_o. That is not arbitrary; it is what keeps integer arithmetic possible. Output i of a layer is a dot product over the inputs j. Write the weights as S_i q_ij and the activations as s_x x_j with integer x_j:

ترسم الشرائح أسهم الـ scale على طول الصفوف، أي قنوات الخرج، وصورة الالتفاف تؤطّر بُعد المرشح c_o. هذا ليس اعتباطيًا؛ بل هو ما يُبقي الحساب بالأعداد الصحيحة ممكنًا. الخرج i لطبقة ما هو جداء نقطي (dot product) على المدخلات j. اكتب الأوزان على شكل S_i q_ij والتنشيطات (activations) على شكل s_x x_j حيث x_j عدد صحيح:

yi=∑jWij aj≈∑j(Si qij)(sx xj)=Si sx∑jqij xj⏟integer accumulatory_i = \sum_j W_{ij}\, a_j \approx \sum_j (S_i\, q_{ij})(s_x\, x_j) = S_i\, s_x \underbrace{\sum_j q_{ij}\, x_j}_{\text{integer accumulator}}
With a scale per output channel, S_i does not depend on j, so it factors out of the sumمع مقياس لكل قناة إخراج، لا تعتمد S_i على j، لذلك تخرج من المجموع
Each S_i factors out of the sum, so the dot product accumulates in integers and is rescaled onceكل S_i يخرج من المجموع كعامل مشترك، فيُراكَم الجداء النقطي بالأعداد الصحيحة ثم يُعاد قياسه مرة واحدة

Because S_i is the same for every term of the sum, the hardware accumulates Σ_j q_ij x_j in an integer register and applies one multiplication by S_i s_x at the end, a step it already performs per output anyway. If the scale instead varied with the input index j, it would sit inside the sum, and every product would need its own rescale before being added, destroying the point of integer accumulation. The same argument is why per-channel scales on activations are hard: an activation scale per input channel cannot be pulled out of the summation (Nagel et al., 2021). This is also why TensorFlow Lite quantizes convolution weights per axis along the output channel dimension.

لأن S_i هو نفسه في كل حد من حدود المجموع، يراكم العتاد Σ_j q_ij x_j في مسجّل (register) للأعداد الصحيحة ثم يطبّق عملية ضرب واحدة في S_i s_x في النهاية، وهي خطوة يؤديها أصلًا لكل خرج على أي حال. لو كان الـ scale يتغيّر بدلًا من ذلك مع فهرس الدخل j، لبقي داخل المجموع، ولاحتاج كل حاصل ضرب إلى إعادة قياس خاصة به قبل جمعه، مما يُفقد المراكمة بالأعداد الصحيحة فائدتها. والحجة نفسها هي سبب صعوبة مقاييس per-channel على التنشيطات: لا يمكن إخراج scale تنشيط خاص بكل قناة دخل من المجموع (Nagel et al., 2021). ولهذا أيضًا يكمّم TensorFlow Lite أوزان الالتفاف لكل محور (per axis) على طول بُعد قناة الخرج.

Quick checkاختبار سريع

Why are per-channel weight scales taken over output channels rather than input channels?لماذا تؤخذ مقاييس الأوزان per-channel على قنوات الخرج وليس على قنوات الدخل؟

Recapالخلاصة

If you remember nothing elseإن لم تتذكر شيئاً آخر فتذكّر هذا

  • PTQ takes a trained floating-point model and picks (S, Z) without retraining. The lecture splits the choice into granularity, dynamic range clipping and rounding.يأخذ PTQ نموذجًا مدرّبًا بالفاصلة العائمة ويختار (S, Z) دون إعادة تدريب. تقسّم المحاضرة هذا الاختيار إلى granularity وdynamic range clipping وrounding.
  • Symmetric weight quantization: Z = 0, |r|_max = |W|_max, S = |r|_max / q_max with q_max = 2^(N-1) - 1, and q = clip(round(W / S)).تكميم الأوزان المتماثل: Z = 0، و|r|_max = |W|_max، وS = |r|_max / q_max حيث q_max = 2^(N-1) - 1، وq = clip(round(W / S)).
  • For N = 2, q_max = 1, so only the codes -1, 0, 1 appear. The code -2 exists in two's complement but the restricted symmetric grid never uses it.عند N = 2 تكون q_max = 1، فلا تظهر إلا الرموز -1 و0 و1. الرمز -2 موجود في تمثيل two's complement لكن الشبكة المتماثلة المقيّدة لا تستخدمه أبدًا.
  • Per-tensor uses one S for the whole tensor. It works for large models but fails on small ones whose output channel ranges differ by more than 100x (the lecture's figure; MobileNetV2 evidence from Nagel et al. 2019).يستخدم per-tensor قيمة S واحدة للـ tensor كله. ينجح مع النماذج الكبيرة لكنه يفشل مع الصغيرة التي تختلف مديات قنوات الخرج فيها بأكثر من 100x (شكل المحاضرة، ودليل MobileNetV2 من Nagel et al. 2019).
  • Per-channel gives output channel i its own S_i = max_j |W_ij| / q_max, so an outlier channel no longer inflates the step size of every other channel.يعطي per-channel قناة الخرج i قيمة خاصة بها S_i = max_j |W_ij| / q_max، فلا تعود القناة الشاذة تضخّم حجم الخطوة لكل قناة أخرى.
  • Worked example: per-tensor S = 2.12, error ||W - S q||_F = 2.28. Per-channel S = (2.09, 2.12, 1.92, 1.87), one code changes (-1.03 goes from 0 to -1), error 2.08.المثال المحلول: في per-tensor تكون S = 2.12 والخطأ ||W - S q||_F = 2.28. وفي per-channel تكون S = (2.09, 2.12, 1.92, 1.87)، ويتغيّر رمز واحد (-1.03 ينتقل من 0 إلى -1)، والخطأ 2.08.
  • Scales must sit on output channels because y_i = S_i s_x Σ_j q_ij x_j: the scale factors out of the integer dot product, so accumulation stays in integers.يجب أن تكون المقاييس على قنوات الخرج لأن y_i = S_i s_x Σ_j q_ij x_j: الـ scale يخرج كعامل مشترك من الجداء النقطي الصحيح، فتبقى المراكمة بالأعداد الصحيحة.

Sourcesالمصادر