跳到主要内容

P5338. 整数缩放因子下的全整型矩阵乘积

场次:2026年9月2日 · 来源:CodeFun2000 P5338

题目描述​

在模型量化中,常需要把高精度浮点数转换为低位整数。对于实数 rr 与缩放因子 ss,线性量化定义为 q=round⁡(r/s)q=\operatorname{round}(r/s),反量化则恢复为 r=sqr=sq。在 nn 比特量化下,缩放因子通常取 s=max⁡(∣rmin⁡∣,∣rmax⁡∣)2n−1−1s=\frac{\max(|r_{\min}|,|r_{\max}|)}{2^{n-1}-1}。矩阵可以按行或按列分别量化,并为每一行或每一列保存一个缩放因子。

为了进一步避免缩放因子参与浮点运算,可以把缩放因子 ss 写成两个整数 a,ba,b 的比值:s=a2bs=\frac{a}{2^b}。这样,两个缩放因子相乘时可以表示为

a12b1×a22b2=a1a22b1+b2.\frac{a_1}{2^{b_1}}\times\frac{a_2}{2^{b_2}} =\frac{a_1a_2}{2^{b_1+b_2}}.

本题要求在这种全整数缩放因子表示下完成矩阵乘法。给定已经按行完成量化的矩阵 AA,以及尚未量化的浮点矩阵 BB。矩阵 AA 的大小为 m×km\times k,矩阵 BB 的大小为 k×nk\times n。需要计算矩阵乘积 C=A×BC=A\times B,并以全整型量化形式输出 CC 的每个元素。

矩阵 AA 的每一行已经附加了缩放因子 sA=aA2bAs_A=\frac{a_A}{2^{b_A}}。矩阵 BB 需要按列进行 8 比特量化:对第 jj 列,先取该列所有元素绝对值的最大值,令该列缩放因子 sB=max⁡(∣B1,j∣,…,∣Bk,j∣)127s_B=\frac{\max(|B_{1,j}|,\dots,|B_{k,j}|)}{127},然后按量化公式得到该列整数表示,并裁剪到 [-127,127];同时将 sBs_B 表示为 aB2bB\frac{a_B}{2^{b_B}} 的形式。

对于结果矩阵 CC 中第 ii 行第 jj 列的元素,其量化值为 AA 第 ii 行量化整数与 BB 第 jj 列量化整数的点积。其缩放因子为两个缩放因子的乘积:

aA2bA×aB2bB=aAaB2bA+bB.\frac{a_A}{2^{b_A}}\times\frac{a_B}{2^{b_B}} =\frac{a_Aa_B}{2^{b_A+b_B}}.

因此该元素需要输出三个整数:点积结果、aAaBa_Aa_B、bA+bBb_A+b_B。点积结果不需要限制在 [-127,127] 内,缩放因子也无需约分。

约束条件:

  • 矩阵 AA 每行给出的已量化整数以及矩阵 BB 量化后的整数均在 -127 到 127 之间。
  • 缩放因子中,aa 的取值范围为 0 到 127,bb 的取值范围为 0 到 30。
  • 矩阵 BB 使用 8 比特量化,量化后的整数范围为 -127 到 127。

输入格式​

第一行包含三个整数 m、k、n,依次表示矩阵 AA 的行数、矩阵 AA 的列数(也是矩阵 BB 的行数)、矩阵 BB 的列数。

接下来 m 行,每行包含 k+2 个整数。前 k 个整数是矩阵 AA 当前行已经量化的整数;最后两个整数依次为该行缩放因子的 a 和 b。

再接下来 n 行,每行包含 k 个浮点数。这些行按矩阵 BB 的转置给出,其中第 i 行表示矩阵 BB 的第 i 列。

输出格式​

输出共 m 行。每一行对应矩阵 CC 的一行,包含 nn 个结果元素。每个结果元素由三个整数依次组成:量化值、缩放因子的 a、缩放因子的 b。同一行内所有整数之间用空格分隔。

示例​

示例 1​

输入:

2 3 2
1 2 3 1 0
10 -5 4 3 2
127.0 0.0 -127.0
0.0 127.0 64.0

输出:

-254 64 6 446 64 6
762 192 8 -379 192 8

说明:

矩阵 AA 第 1 行的缩放因子为 1/201/2^0,即 aA=1,bA=0a_A=1,b_A=0;第 2 行的缩放因子为 3/223/2^2,即 aA=3,bA=2a_A=3,b_A=2。

矩阵 BB 两列的最大绝对值均为 127,因此 sB=127/127=1s_B=127/127=1。将 11 表示为 aB/2bBa_B/2^{b_B} 时,四舍五入得到 aB=64,bB=6a_B=64,b_B=6。

计算点积如下:

  • 第 1 行第 1 列:1×127+2×0+3×(−127)=−2541\times127+2\times0+3\times(-127)=-254。
  • 第 1 行第 2 列:1×0+2×127+3×64=4461\times0+2\times127+3\times64=446。
  • 第 2 行第 1 列:10×127+(−5)×0+4×(−127)=76210\times127+(-5)\times0+4\times(-127)=762。
  • 第 2 行第 2 列:10×0+(−5)×127+4×64=−37910\times0+(-5)\times127+4\times64=-379。

结果缩放因子:

  • 第 1 行:aAaB=1×64=64a_A a_B=1\times64=64,bA+bB=0+6=6b_A+b_B=0+6=6。
  • 第 2 行:aAaB=3×64=192a_A a_B=3\times64=192,bA+bB=2+6=8b_A+b_B=2+6=8。

因此输出如样例输出所示。

示例 2​

输入:

1 1 1
5 1 0
0.0

输出:

0 0 0

说明:

矩阵 BB 唯一列的元素为 [0.0][0.0],最大绝对值为 0,因此 sB=0/127=0s_B=0/127=0,量化后整数仍为 0,缩放因子表示为 0/200/2^0,即 aB=0,bB=0a_B=0,b_B=0。

矩阵 AA 的唯一量化整数为 5,缩放因子为 1/201/2^0。点积为 5×0=05\times0=0。结果缩放因子为 aAaB=1×0=0a_A a_B=1\times0=0,bA+bB=0+0=0b_A+b_B=0+0=0。

因此输出 0 0 0。

示例 3​

输入:

1 2 1
5 -3 2 3
1.0 -2.0

输出:

701 130 15

说明:

矩阵 BB 唯一列的元素为 [1.0,−2.0][1.0,-2.0],最大绝对值为 2,所以 sB=2/127s_B=2/127。

量化时:

  • 1.0/(2/127)=63.51.0/(2/127)=63.5,四舍五入为 64;
  • −2.0/(2/127)=−127-2.0/(2/127)=-127,四舍五入为 -127。

缩放因子 2/1272/127 按照 aB/2bBa_B/2^{b_B} 表示并四舍五入,得到 aB=65,bB=12a_B=65,b_B=12。

矩阵 AA 的量化整数为 [5,−3][5,-3],缩放因子为 2/232/2^3。点积为 5×64+(−3)×(−127)=7015\times64+(-3)\times(-127)=701。结果缩放因子为 aAaB=2×65=130a_A a_B=2\times65=130,bA+bB=3+12=15b_A+b_B=3+12=15。

因此输出 701 130 15。


解题思路​

第一步:理解问题本质​

量化把浮点数 rr 压成整数 qq:

q=round⁡(r/s),r≈sqq=\operatorname{round}(r/s),\qquad r\approx sq

其中 ss 是缩放因子。本题要求缩放因子本身也不用浮点表示,而是写成两个整数:

s=a2bs=\frac{a}{2^b}

这样做的好处是缩放因子之间的乘法完全变成整数运算:

a12b1×a22b2=a1a22b1+b2\frac{a_1}{2^{b_1}}\times\frac{a_2}{2^{b_2}}=\frac{a_1a_2}{2^{b_1+b_2}}

于是结果矩阵 CC 的元素可以拆成三部分独立计算:

  1. 量化值:AA 第 ii 行的量化整数与 BB 第 jj 列的量化整数做点积,全程整数;
  2. 分子 aC=aA⋅aBa_C=a_A\cdot a_B;
  3. 指数 bC=bA+bBb_C=b_A+b_B。

注意题面要求“缩放因子无需约分”,所以 aCa_C 就是两个分子直接相乘,不要除以最大公约数。

第二步:暴力解法——先反量化,再浮点相乘​

最符合直觉的做法是:

# 1. 用 s_A 把 A 的量化整数还原成浮点
A_float = [[q * (a_A / 2 ** b_A) for q in row] for row in A]

# 2. 对 B 的每一列做 8 比特量化,再反量化
B_float = [[q * (a_B[j] / 2 ** b_B[j]) for q in col] for col in B_quant]

# 3. 普通浮点矩阵乘法得到 C
C = [[sum(A_float[i][t] * B_float[t][j] for t in range(k))
for j in range(n)] for i in range(m)]

这个做法能算出大致正确的数值,但得不到题目要的输出:

  • 输出要求的是“点积、aAaBa_Aa_B、bA+bBb_A+b_B”三个整数,而不是 CC 的浮点值;
  • 反量化再相乘会引入浮点误差,aCa_C 与 bCb_C 还要再从浮点值反推,容易在取整上差 1;
  • 缩放因子一旦反量化成浮点,就丢失了“不约分”的信息。

所以正确路线不是“还原浮点”,而是把整数表示一路保持到底。

第三步:优化解法——按列量化 B,用整数点积​

矩阵 AA 已经是量化好的整数,缩放因子也直接给出,所以只需要处理 BB。

B 的量化必须按列进行。 第 jj 列取

Mj=max⁡i∣Bi,j∣,sj=Mj127M_j=\max_i |B_{i,j}|,\qquad s_j=\frac{M_j}{127}

然后

qi,j=clip⁡(round⁡(Bi,j/sj), −127, 127)q_{i,j}=\operatorname{clip}\bigl(\operatorname{round}(B_{i,j}/s_j),\ -127,\ 127\bigr)

代入 sj=Mj/127s_j=M_j/127,量化公式可以少做一次除法:

Bi,j/sj=Bi,j⋅127MjB_{i,j}/s_j=B_{i,j}\cdot\frac{127}{M_j}

代码里直接用 value * 127 / max_abs,避免先算出 sjs_j 再除带来的额外舍入。

缩放因子 sjs_j 转成 a/2ba/2^b。 这里的关键是 bb 取多少。已知 aa 必须是 0∼1270\sim127 的整数,而 bb 最多 3030。直观上希望分母 2b2^b 尽量大(精度更高),但不能让分子超过 127127:

max⁡ b 使得 round⁡(sj⋅2b)≤127\max\ b\ \text{使得}\ \operatorname{round}\Bigl(s_j\cdot 2^b\Bigr)\le 127

例如样例 3 中 sB=2/127≈0.0157480s_B=2/127\approx0.0157480:

bbsB⋅2bs_B\cdot2^b四舍五入后的 aa是否可用
111132.251932.25193232可以,但分母偏小
121264.503964.50396565可以,且是最大的可用 bb
1313129.0079129.0079129129超过 127127,不可用

所以取 aB=65a_B=65、bB=12b_B=12。

结果矩阵。 对每个 (i,j)(i,j):

doti,j=∑t=0k−1qi,tA⋅qt,jB,a=aA[i]⋅aB[j],b=bA[i]+bB[j]\text{dot}_{i,j}=\sum_{t=0}^{k-1}q^A_{i,t}\cdot q^B_{t,j},\qquad a=a_A[i]\cdot a_B[j],\qquad b=b_A[i]+b_B[j]

点积是纯整数运算,不会有任何误差;题面明确说点积不需要裁剪到 [−127,127][-127,127]。

第四步:完整实现细节​

几个必须单独处理的情况:

  1. 整列为 0:Mj=0M_j=0,sj=0/127=0s_j=0/127=0,此时不能做除法。规定量化值全为 00,缩放因子写成 0/200/2^0,即 aB=bB=0a_B=b_B=0(与样例 2 一致)。

  2. 四舍五入的方向:Python 内置的 round 是“银行家舍入”(round(63.5) == 64、round(64.5) == 64),与题面的四舍五入不一致。需要一个 .5 时远离 0 的取整函数:

    def round_half_away(value):
    if value >= 0:
    return int(math.floor(value + 0.5))
    return int(math.ceil(value - 0.5))

    样例 3 里 1.0/(2/127)=63.51.0/(2/127)=63.5 必须得到 6464。

  3. B 是按列给出的:输入的后 nn 行,每行是矩阵 BB 的一个列,不是一行。读进来时不要再转置一次。

  4. aAa_A 可能为 00:AA 那一行的缩放因子分子为 00 时,结果的 aC=0a_C=0,但 bCb_C 仍然要把 bAb_A 加上,不能直接输出 0 0 0。

  5. 输出格式:共 mm 行,每行 nn 组 “点积 aa bb”,组与组之间用一个空格分隔。


完整代码实现​

import math
import sys

# 8 比特量化,缩放因子的分子上限。
MAX_QUANT = 127
# 缩放因子的指数上限,题面给出 0 <= b <= 30。
MAX_SHIFT = 30


def round_half_away(value: float) -> int:
"""四舍五入(.5 向远离 0 的方向取整)。"""
if value >= 0:
return int(math.floor(value + 0.5))
return int(math.ceil(value - 0.5))


def quantize_column(values, max_abs):
"""
把一列浮点数量化成 [-127, 127] 内的整数。

s_B = max_abs / 127,所以 value / s_B = value * 127 / max_abs。
直接用乘法再除,避免先算出 s_B 引入一次额外的舍入。
"""
if max_abs == 0.0:
# 整列都是 0:缩放因子退化为 0,量化值也只能是 0。
return [0] * len(values)

result = []
for value in values:
q = round_half_away(value * MAX_QUANT / max_abs)
# 理论上 |value| <= max_abs 已经保证 |q| <= 127,
# 这里再裁剪一次,挡住浮点误差带来的 128 / -128。
if q > MAX_QUANT:
q = MAX_QUANT
elif q < -MAX_QUANT:
q = -MAX_QUANT
result.append(q)
return result


def column_scale(max_abs: float):
"""
把 s_B = max_abs / 127 表示成 a / 2^b。

b 取“四舍五入后的 a 不超过 127”的最大值:b 越大,分母越细,
表示精度越高;直到 a 再往上就会越过 8 比特缩放因子的上限。
"""
if max_abs == 0.0:
# 与样例 2 一致:0 表示为 0 / 2^0。
return 0, 0

scale = max_abs / MAX_QUANT

# 从大到小找第一个满足 a <= 127 的 b,找到即可返回。
for shift in range(MAX_SHIFT, -1, -1):
a = round_half_away(scale * (1 << shift))
if a <= MAX_QUANT:
return a, shift

# 防御性分支:s_B 大到即使 b = 0 也超过 127(题面约束下不会出现)。
return MAX_QUANT, 0


def solve():
"""读入 A、B,按列量化 B,输出 C 的每个元素与对应缩放因子。"""
data = sys.stdin.buffer.read().split()
if len(data) < 3:
return

it = iter(data)
m = int(next(it))
k = int(next(it))
n = int(next(it))

# A 的每行:k 个量化整数 + 缩放因子的 a、b。
quant_a = []
scale_a = []
for _ in range(m):
row = [int(next(it)) for _ in range(k)]
a = int(next(it))
b = int(next(it))
quant_a.append(row)
scale_a.append((a, b))

# B 按列给出:第 j 行就是 B 的第 j 列。
quant_b = []
scale_b = []
for _ in range(n):
values = [float(next(it)) for _ in range(k)]
max_abs = max(abs(v) for v in values)
quant_b.append(quantize_column(values, max_abs))
scale_b.append(column_scale(max_abs))

lines = []
for i in range(m):
row_a = quant_a[i]
a_a, b_a = scale_a[i]
parts = []
for j in range(n):
col_b = quant_b[j]
# 全整型点积:两个量化整数数组逐项相乘再求和。
dot = sum(row_a[t] * col_b[t] for t in range(k))
a_b, b_b = scale_b[j]
# 缩放因子相乘:分子相乘,指数相加,绝不约分。
parts.append(f"{dot} {a_a * a_b} {b_a + b_b}")
lines.append(" ".join(parts))

print("\n".join(lines))


if __name__ == "__main__":
solve()

NumPy 版本​

以下版本使用 NumPy。只有在评测环境允许第三方库时才能提交;CodeFun2000 的普通 Python 环境不一定安装 NumPy。

思路与上面的纯 Python 版完全一致,只是把逐个元素的循环换成矩阵运算:整列量化用广播一次做完,3131 个候选 bb 也一起算,最后用一次 quant_a @ quant_b.T 得到全部 m×nm\times n 个点积。量化整数都在 [−127,127][-127,127] 内,int64 累加既不会溢出,也没有任何浮点误差。

import sys

import numpy as np

# 8 比特量化,缩放因子的分子上限。
MAX_QUANT = 127
# 缩放因子的指数上限,题面给出 0 <= b <= 30。
MAX_SHIFT = 30


def round_half_away(values):
"""
四舍五入(.5 向远离 0 的方向取整),支持 ndarray 广播。

直接用 np.round 是错误的:它是“银行家舍入”,round(63.5) 会得到 64、
round(64.5) 也会得到 64。这里对非负数加 0.5 向下取整、对负数减 0.5
向上取整,与纯 Python 版逐元素等价。
"""
values = np.asarray(values, dtype=np.float64)
return np.where(values >= 0.0, np.floor(values + 0.5), np.ceil(values - 0.5))


def quantize_columns(values, max_abs):
"""
把 n 列浮点数量化成 [-127, 127] 内的整数。

values 的形状是 n x k(第 j 行是 B 的第 j 列),max_abs 是每列的 M_j。
s_B = M_j / 127,所以 value / s_B = value * 127 / M_j:整列一次性广播做完,
除法次序与纯 Python 版一致,舍入结果逐位相同。
"""
# 整列全 0 时 M_j = 0,先换成一个安全分母避免除零告警,稍后再把这行置 0。
safe_max = np.where(max_abs == 0.0, 1.0, max_abs)
scaled = values * MAX_QUANT / safe_max[:, None]
# 裁剪挡住浮点误差带来的 128 / -128;点积结果不裁剪,那是另一回事。
quant = np.clip(round_half_away(scaled), -MAX_QUANT, MAX_QUANT).astype(np.int64)
quant[max_abs == 0.0] = 0
return quant


def column_scales(max_abs):
"""
把每列的 s_B = M_j / 127 表示成 a / 2^b,返回 (a, b) 两个向量。

b 取“四舍五入后的 a 不超过 127”的最大值。这里是 31 个 b 一起算、
再沿 b 从大到小取第一个可用值,等价于纯 Python 版的倒序循环。
"""
n = max_abs.shape[0]
# b 从 30 递减到 0;argmax 取第一个 True,正好就是最大的可用 b。
shifts = np.arange(MAX_SHIFT, -1, -1)
scale = max_abs / MAX_QUANT
a_all = round_half_away(scale[None, :] * (1 << shifts)[:, None]) # (31, n)
valid = a_all <= MAX_QUANT

cols = np.arange(n)
idx = valid.argmax(axis=0)
a_b = a_all[idx, cols].astype(np.int64)
b_b = shifts[idx].astype(np.int64)

# 防御性分支:s_B 大到即使 b = 0 也超过 127(题面约束下不会出现)。
no_valid = ~valid.any(axis=0)
a_b[no_valid] = MAX_QUANT
b_b[no_valid] = 0

# 与样例 2 一致:整列全 0 记作 0 / 2^0。
zero_cols = max_abs == 0.0
a_b[zero_cols] = 0
b_b[zero_cols] = 0
return a_b, b_b


def solve():
"""读入 A、B,按列量化 B,用矩阵乘法一次算出全部点积。"""
tokens = sys.stdin.buffer.read().split()
if len(tokens) < 3:
return

m, k, n = (int(x) for x in tokens[:3])
pos = 3

# A 的每行:k 个量化整数 + 缩放因子的 a、b,整块读成 (m, k+2)。
block = np.array(tokens[pos:pos + m * (k + 2)], dtype=np.int64).reshape(m, k + 2)
pos += m * (k + 2)
quant_a = block[:, :k]
a_a = block[:, k]
b_a = block[:, k + 1]

# B 按列给出:后 n 行每行就是 B 的一个列向量,直接读成 n x k。
values = np.array(tokens[pos:pos + n * k], dtype=np.float64).reshape(n, k)
max_abs = np.abs(values).max(axis=1)
quant_b = quantize_columns(values, max_abs)
a_b, b_b = column_scales(max_abs)

# 点积 = A 的行 × B 的列,一次 matmul 就能算出 m x n 个结果。
# A、B 的量化整数都在 ±127 内,int64 累加不会有任何溢出或精度损失。
dot = quant_a @ quant_b.T

# 缩放因子:分子相乘(不约分),指数相加。
a_out = np.outer(a_a, a_b)
b_out = b_a[:, None] + b_b[None, :]

# 每个元素输出“点积 a b”三个整数,按行拼成一条字符串。
triples = np.stack([dot, a_out, b_out], axis=-1).reshape(m, -1)
for row in triples.tolist():
print(" ".join(map(str, row)))


if __name__ == "__main__":
solve()

示例推演​

样例 1​

输入 m=2m=2、k=3k=3、n=2n=2。

整理 A:

行量化整数aAa_AbAb_A缩放因子
11,2,31,2,311001/20=11/2^0=1
210,−5,410,-5,433223/22=0.753/2^2=0.75

按列整理 B(输入已经是列):

列元素MjM_jsj=Mj/127s_j=M_j/127
1127.0, 0.0, −127.0127.0,\ 0.0,\ -127.012712711
20.0, 127.0, 64.00.0,\ 127.0,\ 64.012712711

两列的 MjM_j 都是 127127,所以 sj=1s_j=1,量化值就是原来的整数:

  • 第 1 列:(127, 0, −127)(127,\ 0,\ -127);
  • 第 2 列:(0, 127, 64)(0,\ 127,\ 64)。

求 aB,bBa_B,b_B:

bb1⋅2b1\cdot2^b四舍五入是否可用
6664646464可用
77128128128128超过 127127

所以 aB=64a_B=64、bB=6b_B=6。

点积:

  • 第 1 行第 1 列:1×127+2×0+3×(−127)=127−381=−2541\times127+2\times0+3\times(-127)=127-381=-254;
  • 第 1 行第 2 列:1×0+2×127+3×64=254+192=4461\times0+2\times127+3\times64=254+192=446;
  • 第 2 行第 1 列:10×127+(−5)×0+4×(−127)=1270−508=76210\times127+(-5)\times0+4\times(-127)=1270-508=762;
  • 第 2 行第 2 列:10×0+(−5)×127+4×64=−635+256=−37910\times0+(-5)\times127+4\times64=-635+256=-379。

缩放因子:

  • 第 1 行:a=1×64=64a=1\times64=64,b=0+6=6b=0+6=6;
  • 第 2 行:a=3×64=192a=3\times64=192,b=2+6=8b=2+6=8。

按“点积 aa bb”拼接:

-254 64 6 446 64 6
762 192 8 -379 192 8

样例 2​

BB 唯一的列是 [0.0][0.0],M=0M=0,于是 s=0s=0,量化值 q=0q=0,缩放因子写作 0/200/2^0,即 aB=0,bB=0a_B=0,b_B=0。

点积为 5×0=05\times0=0,a=1×0=0a=1\times0=0,b=0+0=0b=0+0=0,输出:

0 0 0

样例 3​

BB 唯一的列是 [1.0,−2.0][1.0,-2.0],M=2M=2,所以 sB=2/127s_B=2/127。

量化:

  • 1.0×127/2=63.51.0\times127/2=63.5,远离 0 取整得 6464;
  • −2.0×127/2=−127-2.0\times127/2=-127,取整得 −127-127。

缩放因子: 前面已经算过,b=12b=12 时 a=round⁡(64.5039)=65a=\operatorname{round}(64.5039)=65,b=13b=13 时 a=129>127a=129>127,所以 aB=65,bB=12a_B=65,b_B=12。

点积: 用 AA 的量化整数 [5,−3][5,-3]:

5×64+(−3)×(−127)=320+381=7015\times64+(-3)\times(-127)=320+381=701

结果缩放因子:aA=2,bA=3a_A=2,b_A=3,于是

a=2×65=130,b=3+12=15a=2\times65=130,\qquad b=3+12=15

输出:

701 130 15

复杂度分析​

解法时间复杂度空间复杂度说明
反量化为浮点再矩阵乘O(nk+mnk)O(nk+mnk)O(mk+nk)O(mk+nk)结果不是题目要求的整数三元组
按列量化 + 整数点积O(nk+mnk)O(nk+mnk)O(mk+nk)O(mk+nk)本题采用;无浮点累积误差
逐元素输出O(mn)O(mn)O(1)O(1)与矩阵乘同阶,可忽略

其中 nknk 用于量化 BB 的每一列(每列求一次最大值、kk 次量化),mnkmnk 用于 m×nm\times n 个点积。主循环里 b 的搜索最多 3131 次,是常数。


易错点总结​

1. B 是按列给出的​

输入的后 nn 行,每行就是矩阵 BB 的一个列向量。不要把它当成行再转置一次,否则量化时求的是“行的最大绝对值”,结果全错。

2. 量化时除以的是 sB=M/127s_B=M/127​

等价于乘以 127/M127/M。写成 value / (max_abs / 127) 与 value * 127 / max_abs 在数学上相同,但后者少一次中间舍入,推荐后者。

3. .5 要远离 0 取整​

Python 的 round 是银行家舍入:round(64.5) == 64。本题要求四舍五入,63.563.5 必须变成 6464,−63.5-63.5 必须变成 −64-64。必须自己实现 round_half_away。

4. 量化值要裁剪,点积不要裁剪​

BB 的量化结果要 clip 到 [−127,127][-127,127];而 CC 的点积是累加结果,题面明确说不需要限制在 [−127,127][-127,127]。样例 1 的 762762 就超过了 127127。

5. bBb_B 不是固定值​

不能写死 bB=7b_B=7 或 bB=30b_B=30。正确做法是取“使四舍五入后的 aBa_B 不超过 127127 的最大 bb”。样例 1 得到 66,样例 3 得到 1212。

6. Mj=0M_j=0 时不能做除法​

整列都是 00 时 sB=0s_B=0,除法会抛异常。此时量化值全为 00,且按样例 2 的约定输出 aB=0,bB=0a_B=0,b_B=0。

7. aA=0a_A=0 时 bb 仍要相加​

结果的分子是 aA⋅aBa_A\cdot a_B,只要有一个为 00 就是 00;但指数是 bA+bBb_A+b_B,与分子是否为 00 无关。写成“a=0a=0 就直接输出 0 0 0”是错的。

8. 缩放因子不要约分​

192=3×64192=3\times64 不能约成 33 或 4848,bb 也不能跟着减。题面明确要求“缩放因子也无需约分”。

9. 输出格式是“三个整数一组”​

每个结果元素占三个数:点积、aa、bb。同一行的 nn 组之间用空格分隔,共输出 mm 行。


扩展思考​

  • 为什么 aa 限制在 127127 以内:aa 与量化值共享同一个 8 比特整数范围,便于用 int8 或定点指令打包存储;a=127a=127 对应最大精度。
  • bb 的上限为什么是 3030:bb 表示分母的二进制指数,2302^{30} 已经能覆盖常见的动态范围;再大就需要更宽的整数类型。
  • 量化误差:对称量化的最大绝对误差是 s/2s/2,相对误差与数值大小有关。整列为 0 时不需要缩放因子,这也是要单独处理的原因。
  • 非对称量化:如果数据整体偏移(例如全为正的大数),可以引入零点 zz,用 q=round⁡(r/s)+zq=\operatorname{round}(r/s)+z,在同样比特数下精度更高。
  • 更省算力的做法:若 bA+bBb_A+b_B 固定,可以把点积结果直接右移相应位数得到近似的浮点值;本题要求保留原始三元组,所以不做移位。
  • 批量矩阵乘:实际推理框架会把多个 (a,b)(a,b) 打包成向量化指令,一次算完整行;本题只需要按定义逐个元素输出。

相关题目​

自测​

加载评论中...