Majid Al-RaimiInside EIE: dataflow, the PE and its storage formats

COE 592Lecture 4.2Part 08

Inside EIE: dataflow, the PE and its storage formats

How a nonzero activation walks through the PE array, what one processing element contains, how load is balanced, and how activation sparsity, CSC weight storage and 4-bit codebooks are realised in hardware.

Concepts
3
Slides
61-70
Reading
18 min
Understood
0/3 concepts

Why this part matters

Part 07 argued that a pruned, quantized network is only fast if the hardware never looks at a zero. This part is where that argument turns into wires and SRAM words. It follows one nonzero activation from the moment the controller finds it to the moment its products land in output registers, opens a single processing element to see the five stages inside, and then reads the storage format bit by bit.

Exam questions on EIE ask for exactly these three things: the dataflow, the PE stages in order, and the storage format with bit widths. Your research project on embedded inference needs the same reasoning whenever you choose a sparse format or an accelerator, because the questions never change: how much metadata does each nonzero cost, how do you keep parallel units equally busy, and how much of the model fits on chip. The running example is the same 8 x 4 matrix on four PEs that part 07 introduced.

By the end you can

  1. Trace a sparse matrix-vector product through EIE's broadcast-and-accumulate dataflow for a given input vector, listing which PEs work on each broadcast.
  2. Name the five PE stages in order and state what data crosses each boundary.
  3. Encode a column in EIE's relative-index CSC with 4-bit indices, including padding, and derive the pointer array.
  4. Explain how the activation FIFO, leading nonzero detection, even and odd pointer banks and the bypass path each remove a stall.
  5. Quote the key EIE numbers: 4-bit weights and indices, a 16-entry codebook, 16-bit fixed point, FIFO depth 8, 64 PEs and 162 KB of SRAM per PE.

Start with the picture from part 07. An 8 x 4 weight matrix is split across four processing elements by interleaving rows: PE0 owns rows 0 and 4, PE1 rows 1 and 5, PE2 rows 2 and 6, PE3 rows 3 and 7. In general, with N PEs, PE k holds every row i with i mod N = k, together with the output b_i it will produce and the input a_i of the same index (Han et al., 2016). The input vector for the layer is a = (0, a1, 0, a3): two of its four entries are zero, which is typical of what ReLU leaves behind.

The whole computation is a sequence of broadcasts. A central control unit scans a for its next nonzero, finds a1 at index 1, and sends the pair (a1, 1) to every PE at once. Each PE then does something entirely local: it looks up column 1 of its own rows in compressed form, multiplies each nonzero weight it finds there by a1, and adds the product into the output entry for that row. When every PE has finished (or, as the next concept shows, has at least queued the work), the controller finds the next nonzero, a3, and the same thing happens for column 3. Columns 0 and 2 are never visited, because the activations that would multiply them are zero, and W x 0 = 0. Inside each visited column, the zero weights are never visited either, because 0 x A = 0 and the compressed format simply does not store them.

a1 and then a3 reach all four PEs; PE0, PE2 and PE3 find work for a1, PE0 and PE2 for a3, each PE ticks off only the nonzero weights it owns in that column, and five of the eight outputs receive a contribution

Worked example

Two broadcasts finish the layer

  1. Skip a0

    The leading nonzero detector reports that the first nonzero of a is a1, not a0. Column 0 holds w00 (PE0) and w50 (PE1), and neither weight is ever read.
  2. Broadcast (a1, 1)

    PE0 walks its column-1 slice and finds w01, so b0 += w01 a1. PE1 finds nothing in column 1. PE2 finds w21, so b2 += w21 a1. PE3 finds w71, so b7 += w71 a1. Multiplies this round: 1, 0, 1, 1.
  3. Skip a2

    Column 2 holds w12 (PE1) and w42 (PE0). Both stay untouched.
  4. Broadcast (a3, 3)

    PE0 finds w03 then w43: b0 += w03 a3 and b4 += w43 a3. PE2 finds w23 then w63: b2 += w23 a3 and b6 += w63 a3. PE1 and PE3 have nothing in column 3. Multiplies this round: 2, 0, 2, 0.
  5. Seven multiplies instead of thirty-two

    OutputContributionsOwnerWhen
    b0w01 a1 + w03 a3PE0Both broadcasts
    b2w21 a1 + w23 a3PE2Both broadcasts
    b4w43 a3PE0Second broadcast only
    b6w63 a3PE2Second broadcast only
    b7w71 a1PE3First broadcast only
    b1, b3, b5no contributionPE1, PE3, PE1Never touched
    What each output entry received
    A dense multiply would have done 8 x 4 = 32 multiply-adds. EIE did 3 + 4 = 7, about 22%, and touched only the weights whose activation and value were both nonzero. ReLU then runs over b: the slide draws b2, b4 and b7 as negative, so they become zero, and the next layer's detector will skip them in turn.

Written as a rule, each output is a sum over only the intersection of two index sets. X_i is the set of columns where row i has a nonzero weight, fixed once pruning is done, and Y is the set of nonzero activations, which changes with every input. That is the static versus dynamic distinction from part 07 written into the arithmetic: weight sparsity shrinks X_i before deployment, activation sparsity shrinks Y at run time, and the hardware only pays for pairs in both.

bi=ReLU(jXiYS[Iij]aj)b_i = \mathrm{ReLU}\Big(\sum_{j \in X_i \cap Y} S[I_{ij}]\, a_j\Big)
One output activation. S is the 16-entry shared table and I_ij the 4-bit code stored for weight (i, j)

The S[I_ij] inside the sum is a reminder that the PE never stores the weight itself, only a 4-bit code into a shared table, which the third concept unpacks. Notice also what the four PEs did not do: they never exchanged a partial sum. Every product for row i is produced in the one PE that owns row i, so the only communication in the whole layer is the broadcast of (a_j, j) pairs, which the paper carries on an H-tree so that every PE receives it in the same cycle (Han et al., 2016).

Recall

What does the central control unit broadcast, and what does each PE do with it?

The nonzero activation a_j together with its index j. Each PE reads the pointers p_j and p_(j+1), walks its own entries for column j, decodes each weight, works out the absolute row, and accumulates v x a_j into that row's output register.

Recall

In the 8 x 4 example with a = (0, a1, 0, a3), which output entries receive a contribution, and how many multiplies happen in total?

b0, b2, b4, b6 and b7. Seven multiplies: three for a1 and four for a3, against 32 for the dense product.

Quick check

In EIE, how is the weight matrix divided among the processing elements?

Now stand inside PE0 while (a3, 3) arrives. The pair lands in a small queue at the front of the PE. The index 3 is used to look up where column 3 starts and ends in this PE's compressed storage. Those two addresses fetch the entries for the column, each an encoded weight plus a relative row index. The weight code is expanded to a real number, the relative index is turned into an absolute row, the multiply and add happen, and the result is written into the destination register for that row. When the layer is complete, ReLU and a leading nonzero detector turn the destination registers into the next layer's input. The slide draws this as the queue, then four dashed regions, then a ReLU and detector tail, and every EIE exam answer should be able to name the five stages in order.

Act Queue
value + index, depth 8

Holds broadcast (a_j, j) pairs so this PE can lag or lead the others.

index j
Pointer Read
even and odd SRAM banks

Reads p_j and p_(j+1), the start and end of column j, in one cycle.

start, end address
Sparse Matrix Access
128 KB Spmat SRAM

Fetches 8-bit entries, one 4-bit code and one 4-bit relative index each.

Arithmetic Unit
decoder, address accumulator, multiply, add

Code becomes a 16-bit weight, relative index becomes an absolute address, product is added with a bypass for back-to-back hits.

updated activation
Act R/W
dest and src register files, 2 KB SRAM

Destination file collects this layer, source file feeds it; they swap roles at the next layer.

after the layer
ReLU and leading nonzero detect
feeds the next layer

Clamps negatives to zero and finds the next nonzero to broadcast.

One processing element, left to right, with what crosses each boundary

The same trace, with the paper's bit widths

The queue head is (a3, 3). The pointer unit reads two 16-bit pointers, p3 and p4. In PE0's arrays for the running example they are 3 and 5 (the third concept derives them), so column 3 occupies entries 3 and 4. The sparse matrix unit fetches those entries. Each is 8 bits: a 4-bit weight code and a 4-bit relative index. The SRAM is 64 bits wide, so a single read returns eight entries; the high 13 bits of a pointer select the SRAM row and the low 3 bits select the entry inside it, and the unit hands one entry per cycle to the arithmetic unit (Han et al., 2016). Why eight? With 64 PEs and about 10 percent density, a column of length 4096 leaves each PE about 6.4 nonzeros per column, so one 64-bit read usually covers a whole column.

The two entries for column 3 are (code of w03, x = 0) and (code of w43, x = 0). The weight decoder expands each 4-bit code through the layer's 16-entry table into a 16-bit fixed-point value. The address accumulator keeps a running sum of the relative indices plus one per entry, producing local rows 0 and 1, which are absolute rows 0 and 4 for PE0. The multiplier forms a3 x w03, the adder adds it to destination register 0, then a3 x w43 is added to destination register 1. Had two consecutive entries hit the same register, the adder's output would have been routed straight back to its input through the bypass path instead of waiting for the register write (Han et al., 2016).

The activation read and write unit is two register files, source and destination, each holding 64 activations of 16 bits, enough for a 4096-long vector across 64 PEs. The destination file collects this layer's outputs while the source file supplies this layer's inputs; when the layer is done they exchange roles, so no activation is ever copied between layers. Vectors longer than 4096 spill into a 2 KB activation SRAM and are processed in batches (Han et al., 2016).

Column j = 5 needs p5 and p6. Their addresses differ in the least significant bit, so they always sit in different banks and both reads finish in one cycle

The pointer read deserves one more sentence, because it is a favorite exam question. A single-ported SRAM serves one read per cycle, but every column needs two pointers. EIE stores the pointers in two banks and uses the least significant bit of the address to choose the bank. Since p_j and p_(j+1) are adjacent addresses, they always land in different banks, and both are read in the same cycle without paying for a dual-ported (larger, hotter) memory (Han et al., 2016).

What one PE holds

Activation value
16-bit fixed point
Column pointer
16 bits
Sparse matrix entry
8 bits = 4-bit code v + 4-bit relative index x
One SRAM read
64 bits = 8 entries
Pointer split
13 high bits pick the SRAM row, 3 low bits pick the entry
Activation register files
2 files x 64 entries x 16 bits (source and destination)
Activation SRAM
2 KB
Pointer SRAM
32 KB (two banks)
Sparse matrix SRAM
128 KB
SRAM per PE
162 KB (93% of PE area, 59% of PE power)
Area and power per PE
0.638 mm², 9.157 mW at 800 MHz, TSMC 45 nm

The last two rows explain the shape of the whole chip. SRAM is 93 percent of a PE's area and 59 percent of its power, and 64 PEs fill about 40.8 mm² at 590 mW (Han et al., 2016). That is the point, not a flaw: the paper's energy argument starts from the fact that a 32-bit DRAM access costs 640 pJ against 5 pJ for SRAM, about 128 times, which the paper rounds to a 120x energy saving, so a compressed model that fits entirely on chip wins before any arithmetic trick is counted. Everything in this PE is arranged to keep the model small enough to stay there.

Recall

Why can p_j and p_(j+1) always be read in one cycle?

They are adjacent addresses, so their least significant bits differ. EIE uses that bit to select between two single-ported banks, even and odd, so the two pointers are always in different banks and can be read simultaneously.

Quick check

What do the even and odd pointer SRAM banks make possible?

Go back to the worked example and count the multiplies per PE: 3, 0, 3, 1. That uneven count is the first of four problems the PE design must solve, and the slides walk through the solutions one dashed region at a time: a queue for load balance, a detector for activation sparsity, a compressed format for weight sparsity, a decoder for weight sharing, and finally the arithmetic and write-back that ties them together. The storage format is the thread through all of them, so this concept ends with its bit widths.

Load balance: let the fast PEs run ahead

Because rows are interleaved, each PE may hold a different number of nonzeros in any given column. In the example, the a3 broadcast gives PE0 and PE2 two multiplies each and PE1 and PE3 none. If the controller waited for every PE before broadcasting the next activation, PE1 and PE3 would idle for two cycles on every such column. The activation queue is the fix. Each PE holds a FIFO of broadcast (value, index) pairs, the controller keeps broadcasting as long as no queue is full, and a PE with little to do in one column simply moves on to the next pair in its queue while a busier PE catches up. The paper puts it plainly: the queue lets each PE build up a backlog of work to even out load imbalance, and the broadcast is disabled only if some PE's queue is full (Han et al., 2016).

Six broadcasts with uneven loads, one broadcast per cycle. With depth 1 each round waits for the slowest PE and the layer takes 11 cycles; with a queue each PE drains its own backlog and the same 24 multiplies finish in 7

The paper measured this. It swept the queue depth from 1 to 256 and defined load balance efficiency as one minus the starvation bubble cycles over the total. At depth 1, around half of all cycles are idle; efficiency climbs steeply to depth 8 and flattens afterwards, so 8 was chosen as the queue depth (Han et al., 2016, Figure 8). The visual above is illustrative and uses small integer loads, but the shape of the result is the paper's: a shallow queue wastes about half the machine.

Activation sparsity: never queue a zero

The queue only helps if what enters it is worth doing. Zero activations must be filtered out before the broadcast, and this is the job of leading nonzero detection. It is distributed, not central: each group of four PEs does a local detection over the activations it stores, the results feed up a quadtree of detector nodes, and the root of that tree is the central control unit, which selects the next nonzero and broadcasts it over an H-tree so that every PE receives it in the same cycle. For64 PEs that is 21 detector units (16 leaves, 4 intermediate, 1 root), each tiny at about 189 µm² and 0.023 mW (Han et al., 2016). Since about 70 percent of activations are zero after ReLU, this alone removes roughly two thirds of the potential broadcasts, which is the 3x factor in the paper's energy accounting.

Weight sparsity: CSC with a relative index

The pointer read and sparse matrix access regions implement the compressed format, a variation of compressed sparse column. Standard CSC, as in SciPy's csc_matrix, stores three arrays: the nonzero values, the row index of each, and a pointer array such that column j occupies positions p_j to p_(j+1) - 1 (SciPy). EIE keeps the values and the pointers but replaces the absolute row index with a relative one: for each nonzero, the number of zeros since the previous nonzero in that PE's slice of the column. Both the value and the relative index are 4 bits. When more than 15 zeros precede a nonzero, the encoder inserts a padding entry with value 0 and index 15 and continues counting from there (Han et al., 2016).

Paper example: a column with nonzeros at rows 2, 3 and 22. Gaps of 2 and 0 encode directly; the gap of 18 exceeds 15, so a padding entry (0, 15) is inserted before (3, 2)

Worked example

Encode PE0's slice of the 8 x 4 example

  1. Write PE0's two rows

    PE0 owns absolute rows 0 and 4, which are local rows 0 and 1. Its slice of the matrix is [w00, w01, 0, w03] over [0, 0, w42, w43].
  2. Walk the columns

    Column 0: w00 at local row 0, no zeros before it, relative index 0. Column 1: w01, index 0. Column 2: local row 0 is zero, then w42 at local row 1, so index 1. Column 3: w03 with index 0, then w43 immediately after with index 0.
  3. Build the pointers

    Column lengths are 1, 1, 1, 2, so the pointer array is the running sum with a final end marker: p = [0, 1, 2, 3, 5]. That is where the p3 = 3 and p4 = 5 of the previous concept came from.
  4. PE0's three arrays

    v = [w00, w01, w42, w03, w43], x = [0, 0, 1, 0, 0], p = [0, 1, 2, 3, 5]. Five 8-bit entries and five 16-bit pointers for a slice that would take eight 16-bit weights dense.
    PEPointer array pEntries per column (p(j+1) minus p(j))
    PE0 (rows 0, 4)[0, 1, 2, 3, 5]1, 1, 1, 2
    PE1 (rows 1, 5)[0, 1, 1, 2, 2]1, 0, 1, 0
    PE2 (rows 2, 6)[0, 0, 1, 1, 3]0, 1, 0, 2
    PE3 (rows 3, 7)[0, 0, 1, 1, 1]0, 1, 0, 0
    All four PEs: pointers and the per-column entry counts they imply
    Read the last column across the rows and you are looking at load imbalance in the data: for column 3, PE0 and PE2 have two entries while PE1 and PE3 have none. The pointer arrays are where the queue's job is written down.
SimulatorCSC encoder: from a sparse column to (v, z) pairs and pointers
8 rows per column
Click a cell to cycle its 4-bit code through 0, 1, 2, 3, 7 and 15. Zero means pruned.
col 0
column 02 entries
  • pos 2v70111z20010
  • pos 7v30011z40100
pointer array p
  • p00
  • end2
Entries stored22 real, 0 padding
Compressed bits48bit8 per entry plus 16 per pointer
Dense 4-bit codes32bitevery cell, no indices
Dense 16-bit weights128bitunquantized, uncompressed

Each nonzero becomes one 8-bit entry: a 4-bit code v and a 4-bit relative index z counting the zeros since the previous entry. Whenever that count would pass 15, the encoder inserts a padding entry (teal) with v = 0 and z = 15, exactly as the EIE paper describes, and the padding entry is multiplied like any other. The pointer array holds one 16-bit start address per column plus one end marker, so p[j+1] minus p[j] is the number of entries the PE walks for column j. At low density the compressed bits fall well below the dense 4-bit total; at high density the index overhead makes compression a loss, which is why EIE only pays off on pruned layers.

Why a relative index at all? For a layer with 4096 rows split across 64 PEs, each PE-local row still needs 6 bits to name absolutely, and the absolute row across the whole matrix needs 12. A 4-bit relative index cuts even the local figure by a third, and, more importantly, matches the 4-bit weight code so that one 8-bit entry carries both. The price is the occasional padding entry, and the paper notes that padding zeros are treated as nonzeros and lead to wasted computation; its Figure 12 shows that more PEs mean fewer padding zeros, because each PE's slice of a column is shorter and long gaps become rarer (Han et al., 2016).

Weight sharing: 4-bit codes, 16-bit arithmetic

The value stored in each entry is not a weight. It is a 4-bit index into a per-layer table of 16 shared weights, the codebook produced by the k-means quantization of Deep Compression (Han, Mao and Dally, 2016). The weight decoder reads the code out of the sparse matrix entry and looks up the 16-bit fixed-point value it stands for, every cycle, on the way to the multiplier. This is the 8x memory factor in the paper's accounting: the SRAM holds 4-bit codes, the multiplier sees 16-bit numbers, and the layer's whole set of distinct weight values is 16 entries long.

PrecisionPrediction accuracyReading
32-bit floating point80.3%Reference
16-bit fixed point79.8%EIE's choice, half a point lost
8-bit fixed point53.0%Collapses, unusable at this width
AlexNet ImageNet prediction accuracy under different arithmetic precisions (the paper says only prediction accuracy; the values match AlexNet's top-5 range)

The 16-bit choice is not arbitrary. The paper reports that 16-bit fixed point costs half a percentage point against 32-bit floating point, while 8-bit fixed point collapses to 53 percent, and that a 16-bit fixed multiply uses about 5 times less energy than a 32-bit fixed multiply and 6.2 times less than a 32-bit floating point one (Han et al., 2016). Four bits of storage and sixteen bits of arithmetic is the compromise that keeps both the SRAM and the accuracy where they need to be.

Arithmetic and write-back

The last region closes the loop. The arithmetic unit computes b_x = b_x + v x a_j, where the address x comes from the address accumulator's running sum of relative indices and v from the decoder. The destination register selected by x is read, the product is added, and the sum is written back. If two adjacent cycles select the same accumulator, the bypass path forwards the adder output directly to its input so the second add does not wait on the register (Han et al., 2016). At the end of the layer the destination file becomes the source file, ReLU and the detector run over it, and the first nonzero of the next layer enters the queue.

Storage formats and bit widths

Weight code v
4 bits, index into the layer's 16-entry codebook
Relative index x (or z)
4 bits, zeros since the previous entry, at most 15
Padding rule
gap above 15: insert v = 0, x = 15, then continue counting
Pointer p(j)
16 bits, start of column j; p(j+1) minus p(j) is the entry count
Codebook
16 entries x 16-bit fixed point, one table per layer
Activation
16-bit fixed point
Activation queue
depth 8, holds (value, index) pairs

Recall

Encode the column [0, 0, 5, 0, 0, 0, 0, 7] in EIE's relative format.

v = [5, 7], z = [2, 4]. Two zeros precede the 5 and four zeros separate the 5 from the 7. No gap exceeds 15, so there is no padding.

Recall

What did the paper measure at FIFO depth 1, and which depth did it choose?

About half of all cycles were idle from starvation. It chose depth 8, beyond which the efficiency curve flattens.

Quick check

A column in one PE's slice has 18 zeros before its next nonzero weight. What does the encoder store?

Quick check

According to the paper, what happens to load balance at an activation FIFO depth of one?

Recap

If you remember nothing else

  • The CCU broadcasts only nonzero activations with their index; every PE multiplies that value by the nonzeros in its own slice of that column and accumulates locally.
  • Rows are interleaved across PEs (row i belongs to PE i mod N); activations are broadcast, never partitioned.
  • The PE pipeline is Act Queue, Pointer Read, Sparse Matrix Access, Arithmetic Unit, Act R/W, then ReLU and leading nonzero detection.
  • Weights live in relative-index CSC: a 4-bit code plus a 4-bit gap in one 8-bit entry, a padding zero whenever a gap exceeds 15, and 16-bit pointers with p(j+1) minus p(j) giving the column's entry count.
  • Even and odd pointer banks read both bounds of a column in one cycle from single-ported SRAM.
  • The 16-entry codebook decodes 4-bit codes to 16-bit fixed point at compute time; 16-bit fixed point costs about half a percentage point of AlexNet accuracy.
  • Load imbalance comes from uneven nonzero counts per column per PE; a FIFO of depth 8 hides it, while depth 1 leaves about half of all cycles idle.
  • The bypass path handles back-to-back updates to the same accumulator; source and destination register files swap roles between layers.
  • SRAM is 93 percent of PE area; one PE is 0.638 mm² and 9.157 mW at 800 MHz in 45 nm.

Sources