To improve data access efficiency and throughput, the Unified Buffer adopts a bank structure (memory modules of the same size). The Unified Buffer has a total size of 256 KB and is divided into 16 banks. Each bank consists of 512 rows, with each row being 32 bytes long. These 16 banks are further organized into eight bank groups, with each bank group containing two banks. For example, bank7 and bank15 form a bank group.

Each bank can independently read and write data, and multiple data requests can be processed at the same time. However, when a plurality of read/write operations attempt to access a same bank at the same time, due to a limitation of hardware resources, these operations need to wait in a queue, resulting in a bank conflict and performance deterioration.
Specifically, the Vector Unit can read or write a row of data from or into each bank group in each cycle (an instruction cycle). When multiple read/write operations attempt to access the same bank at the same time, the Vector Unit cannot process all requests in the same cycle. As a result, these requests wait in a queue. This queuing increases the data access latency and reduces the overall system performance.
Bank conflicts can be classified into the following three scenarios:
The following provides some specific examples. Assume that 0x10000 is on bank0 and 0x10020 is on bank1, as shown in the following figure.

When the source operand src and destination operand dst of the Vector instruction are read and written to the same bank at the same time, a read/write conflict occurs. The details are as follows:
No. |
src addr |
dst addr |
bank |
bank group |
Conclusion |
|---|---|---|---|---|---|
Example 1 |
0x10020 |
0x10000 |
bank_id0 != bank_id1 |
bank_group_id0 != bank_group_id1 |
The src and dst addresses are in bank0 and bank1, respectively. Therefore, no conflict occurs. |
Example 2 |
0x10020 |
0x10120 |
bank_id0 == bank_id1 |
bank_group_id0 == bank_group_id1 |
The src and dst addresses are in bank0. Therefore, a conflict occurs. |
No. |
dst addr |
blk_stride |
block0_addr |
block1_addr |
block2_addr |
... |
Conclusion |
|---|---|---|---|---|---|---|---|
Example 1 |
0x10000 |
8 |
0x10000 |
0x10100 |
0x10200 |
... |
All eight data blocks are in the same bank group. Therefore, conflicts occur. One repeat is written in eight cycles. |
No. |
src0 addr |
src1 addr |
bank |
bank group |
Conclusion |
|---|---|---|---|---|---|
Example 1 |
0x10000 |
0x10100 |
bank_id0 == bank_id1 |
bank_group_id0 == bank_group_id1 |
Conflict |
Example 2 |
0x10000 |
0x10020 |
bank_id0 != bank_id1 |
bank_group_id0 != bank_group_id1 |
No conflict |
No. |
src addr |
blk_stride |
block0_addr |
block1_addr |
block2_addr |
... |
Conclusion |
|---|---|---|---|---|---|---|---|
Example 1 |
0x10000 |
8 |
0x10000 |
0x10100 |
0x10200 |
... |
All eight data blocks are in the same bank. Therefore, conflicts occur. One repeat is read in eight cycles. |
There are two methods to avoid bank conflicts: optimizing the computation logic and optimizing address allocation.
Add 1 to each element of an input with the data type float and shape (16, 64). By changing the computation logic from column-wise to row-wise, conflicts within the same repeat can be avoided. The following table compares the implementation solutions.
Implementation |
Original Implementation |
Optimized Implementation |
||||
|---|---|---|---|---|---|---|
Implementation method |
Column-wise computation: The eight data blocks input in the same repeat are in the same bank, causing a read/read conflict. |
Row-wise computation: The eight data blocks input in the same repeat are not in the same bank, avoiding a read/read conflict within the same repeat. |
||||
Diagram |
|
|
||||
Sample code |
|
|
Implement the addition of 4096 consecutive float elements (z = x + y). By allocating more memory, ensure that x/y and z do not appear in the same bank at the same time within a repeat.
The implementation solutions are compared as follows:
Implementation |
Original Implementation |
Optimized Implementation |
||||
|---|---|---|---|---|---|---|
Implementation method |
No address optimization is performed. InitBuffer is directly used to allocate memory. The addresses of each tensor are as follows: x: The start address is 0x00000, and the tensor length is 4096 x sizeof(float) bytes. y: The start address is 0x04000, and the tensor length is 4096 x sizeof(float) bytes. z: The start address is 0x08000, and the tensor length is 4096 x sizeof(float) bytes. In a repeat, x and y read the same bank group at the same time, and x/y and z read and write the same bank at the same time. |
Optimize the address. When InitBuffer is used to allocate memory, properly allocate more memory. The addresses of each tensor are as follows: x: The start address is 0x00000, and the tensor length is 4096 x sizeof(float) bytes. y: The start address is 0x04000, and the tensor length is (8 x 16 x 1024 – (4096 x sizeof(float))) bytes. z: The start address is 0x20000, and the tensor length is 4096 x sizeof(float) bytes. Allocate more space for y to ensure that z does not fall into the same bank as x or y. |
||||
Diagram |
|
|
||||
Sample code |
|
|