@@ -38,8 +38,8 @@ columns, and makes one carry pass across the column totals.
3838
3939### 1. Build the Times Table
4040
41- The code starts by constructing the 100 entries in the ` 0–9 ` times table. Each
42- product is stored as separate tens and ones:
41+ The code starts by constructing a 100-entry Python mapping for the ` 0–9 ` times
42+ table. Each product is stored as separate tens and ones:
4343
4444``` python
4545for a in range (10 ):
@@ -53,8 +53,9 @@ for a in range(10):
5353```
5454
5555For example, ` 7 × 8 ` is stored as ` (5, 6) ` . This Python loop runs once while
56- building the calculator; the resulting table becomes a fixed part of the
57- finished model.
56+ building the graph. Every later call to ` onehot_lookup ` materializes the
57+ mapping as an independent 100-lane FFN node; the finished transformer does not
58+ contain one shared table queried by every digit pair.
5859
5960### 2. Place the Products into Columns
6061
@@ -67,49 +68,57 @@ product = onehot_lookup(
6768tens = _slice(product, 0 , 1 , name = " product_tens" )
6869ones = _slice(product, 1 , 1 , name = " product_ones" )
6970
70- place = (n - 1 - i) + (n - 1 - j)
71- columns[place].append(ones)
72- columns[place + 1 ].append(tens)
71+ columns[i + j].append(tens)
72+ columns[i + j + 1 ].append(ones)
7373```
7474
7575Each pair gets its own lookup, so all the digit products can be found together.
76- For the default three-digit calculator, there are ` 3 × 3 = 9 ` of these lookups.
77- The ones and tens are then sent to the columns representing their place values.
76+ For the default three-digit calculator, there are ` 3 × 3 = 9 ` independent
77+ lookup circuits, comprising 900 FFN lanes. The operands, columns, and result
78+ are all stored most-significant digit first. For operands at positions ` i ` and
79+ ` j ` , the product's tens go into column ` i + j ` and its ones go into the next
80+ column, ` i + j + 1 ` .
7881
79- For ` 12 × 34 ` , the work looks like this:
82+ For ` 12 × 34 ` , the nonzero columns look like this from most significant to
83+ least significant:
8084
8185| Column | Contributions | Result |
8286| --- | --- | --- |
83- | Ones | ` 2 × 4 ` contributes ` 8 ` | Write ` 8 ` |
84- | Tens | ` 2 × 3 ` contributes ` 6 ` ; ` 1 × 4 ` contributes ` 4 ` | Write ` 0 ` , carry ` 1 ` |
8587| Hundreds | ` 1 × 3 ` contributes ` 3 ` , plus the carry | Write ` 4 ` |
88+ | Tens | ` 2 × 3 ` contributes ` 6 ` ; ` 1 × 4 ` contributes ` 4 ` | Write ` 0 ` , carry ` 1 ` |
89+ | Ones | ` 2 × 4 ` contributes ` 8 ` | Write ` 8 ` |
8690
8791### 3. Make One Carry Pass
8892
8993The last multiplication step moves from the least-significant column to the
9094most-significant one. The essential part of the code is:
9195
9296``` python
93- total = add(sum_nodes(contributions), carry)
94- total_onehot = bool_to_01(
95- in_range(total, add_const(total, 1.0 ), max_total + 1 )
96- )
97- out_lsb_first.append(
98- onehot_lookup(total_onehot, digit_table, embedding.get_embedding(" 0" ))
99- )
100- carry = piecewise_linear(
101- total,
102- sorted (carry_knots),
103- carry_knots.__getitem__ ,
104- input_scale = step_sharpness,
105- name = " carry_staircase" ,
106- )
97+ for column in reversed (columns):
98+ contributions = column or [zero_scalar]
99+ total = add(sum_nodes(contributions), carry)
100+ total_onehot = bool_to_01(
101+ in_range(total, add_const(total, 1.0 ), max_total + 1 )
102+ )
103+ out_lsb_first.append(
104+ onehot_lookup(total_onehot, digit_table, embedding.get_embedding(" 0" ))
105+ )
106+ carry = piecewise_linear(
107+ total,
108+ sorted (carry_knots),
109+ carry_knots.__getitem__ ,
110+ input_scale = step_sharpness,
111+ name = " carry_staircase" ,
112+ )
107113```
108114
109- For each column, ` total ` combines its contributions with the incoming carry.
110- The lookup selects the final decimal digit, while ` carry_staircase ` calculates
111- how many tens move into the next column. This stage is sequential because each
112- column needs the carry produced by the column to its right.
115+ Although ` columns ` is stored most-significant first, carrying must proceed from
116+ right to left, so the loop traverses ` reversed(columns) ` . For each column,
117+ ` total ` combines its contributions with the incoming carry. The lookup selects
118+ the final decimal digit, while ` carry_staircase ` calculates how many tens move
119+ into the next column. This stage is sequential because each column needs the
120+ carry produced by the column to its right. The loop accumulates the output
121+ least-significant digit first, then reverses it before returning the result.
113122
114123The result is held at a fixed width, so ` 12 × 34 ` may initially appear as
115124` 000408 ` . The leading zeros are removed later.
0 commit comments