0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで4bit量子化、小さな更新が毎回0になる理由

0
Posted at

2026年10月14日(UTC)付のarXiv新着告知に、4bit通信と誤差補償を扱う論文Echoが載った。通信する値を小さくすれば軽くなる。ただ、0.02という更新を10回渡しても、受け手の合計が0のままになる条件がある。

自分は今日、この丸めだけをPythonで切り出してて、残差を戻す前後の配列を見比べた。10個とも0だった配列に、1が2個現れる。地味だけど、低精度化の確認では単発の誤差より先に見たい挙動なんだよね。

ここで確かめるのは、一定の刻み幅で丸める小さな実験。Echoの通信アルゴリズムの再現や、GPUでの速度・学習精度の検証は含まない。

Pythonで0.02を10回丸める

刻み幅を0.1に固定し、整数の符号を含めた4bitの範囲から、対称な15段階の-7〜7を使う。復元できる値は-0.7〜0.7だ。残る1通りは使わない。整数コードを返すだけで、実際に4bitずつバイト列へ詰める処理も省いた。

0.02を0.1単位に直すと0.2。これを四捨五入して整数にすると0になる。10回分を先に足せば0.2が残るのに、各回で丸めてから足すと全部消える。

Python 3.14.8で実行した。標準ライブラリだけで動く。Fractionを使うのは、2進浮動小数点の表現誤差を混ぜずに丸めの影響を見るためだ。roundのちょうど中間の値は偶数側へ丸められる。

from fractions import Fraction as F

STEP = F(1, 10)

def run(updates, feedback):
    codes = []
    residual = F(0)
    for x in updates:
        u = x + residual if feedback else x
        q = max(-7, min(7, round(u / STEP)))
        codes.append(q)
        residual = u - q * STEP if feedback else F(0)
    return codes, sum(codes) * STEP, residual

updates = [F(1, 50)] * 10
for enabled in (False, True):
    codes, total, residual = run(updates, enabled)
    print(enabled, codes, total, residual)

codes, total, residual = run(updates, True)
assert total + residual == sum(updates)
assert (total, residual) == (F(1, 5), F(0))

# 表現範囲を超える入力も確認する
_, total, residual = run([F(4, 5)] * 3, True)
print("clipped:", total, residual)
assert (total, residual) == (F(21, 10), F(3, 10))

出力は次のとおり。末尾の2列は、復元した値の合計と、最後に手元へ残った誤差だ。

False [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 0 0
True [0, 0, 1, 0, 0, 0, 0, 1, 0, 0] 1/5 0
clipped: 21/10 3/10

残差を次の入力へ戻すと、何が残るか

feedback=Trueでは、その回に復元できなかった分をresidualに保存する。次回は新しい入力へ足してから丸める。

3回目には、入力0.02と持ち越した0.04が合わさって0.06になる。コード1、つまり0.1を渡し、残差には-0.04を残す。多めに渡した分も、次回の丸めへ反映される。

残差には負の値も入る。

3回目の入力0.02に前回の残差0.04を足し、0.1へ丸めて渡し、残差マイナス0.04を次回へ持ち越す流れ

初期残差が0なら、各回のresidual = u - q * STEPを足し合わせると、中間の残差が打ち消し合う。結果として「入力の累計 = 復元値の累計 + 最後の残差」が保たれる。コードの最初のassertで見ているのは、この関係だ。今回は10回目の残差が0になったので、復元値の累計も元の0.2と一致した。

Fractionの出力が1/5なのも、ログを読むときにはちょっと面倒だ。今回は小数表示に直す処理を入れず、計算をそのまま追える形にした。

ただし、残差を持てば何でも解決するわけではない。

最後の入力は0.8を3回。上限の0.7へ毎回切り詰めるため、入力合計2.4に対して渡せたのは2.1、残差は0.3だった。入力が上限を超え続けると、持ち越す量が増える。同じ関係式が成り立っていても、受け手へ届いていない値は残る。

低精度化で自分が確認したい値

量子化の試験には、小さい同符号の入力を繰り返す列と、上限を超える列を入れておきたい。前者で更新が消える条件、後者で残差がたまる条件を分けて確認できる。この実験から学習精度の維持までは言えず、更新が届くタイミングも変わる。実装を速くする段階でも、送った値の累計と残差を一緒に追える検査は手元に残すつもりだ。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?