# 2020q3 Homework6 (quiz6) contributed by < `Rsysz` > ###### tags: `sysprog` {%hackmd theme-dark %} >[測驗題](https://hackmd.io/@sysprog/2020-quiz6) ## 1. ![](https://i.imgur.com/f3joD87.png) 在深度學習中, `weights`,`gradients` `activations` 等各種 `parameter` 皆涉及滿滿的浮點數運算,因此各種加速運算的方法推陳出新,`FP16` 與 `TPU` 便是 `Google` 所設計的加速技巧,能為 `model training` 帶來以下的好處。 * 搭配專用硬體 `TPU` 能大幅加速運算速度 * 因各種 `parameter` 占用空間的大幅下降,16GB的RAM能當32G的用 * 加速 `training` 過程,使 `cycle` 的時間縮短,加速模型迭代 * 雖然以前有研究表示降低浮點數的精度將會導致模型準確度下降,但根據 `Google` 所做的實驗若只針對 `activations` 採用 `FP16`,將能加速運算且減少對模型準確度的影響。 ```cpp float fp32tobf16(float x) { float y = x; int *py = (int *) &y; unsigned int exp, man; exp = *py & 0x7F800000u; man = *py & 0x007FFFFFu; if (!exp && !man) /* zero */ return x; if (exp == 0x7F800000u) /* infinity or NaN */ return x; /* Normalized number. round to nearest */ float r = x; int *pr = (int *) &r; *pr &= BB1; r /= 256; y = x + r; *py &= BB2; return y; } ``` 根據[BF16wiki](https://en.wikipedia.org/wiki/Bfloat16_floating-point_format)兩者的結構如下圖所示 ![](https://i.imgur.com/4TkKroY.png) ![](https://i.imgur.com/jXXUAoS.png) * 轉換前針對 `NaN`,`INF`,`0` 等特例先行排除 * 利用BB1 = `(a) 0x7f800000` 取出 `Exp` 部分,根據 `Exp` 部分數值 $2^{Exp-8}$帶入 * $$y = x + r = 2^{Exp}\times1.XX + 2^{Exp-8} = 2^{Exp}(1.XX + 2^{-8})$$ 代表若將 `BF32` 展開,$2^{-8}$部分若有值,將會對 `Fraction` 部分進位 ```graphviz digraph SR{ node[shape=record] BF16 [label="<f0>0|<f1>0|<f2>1|<f3>1|<f4>1|<f5>1|<f6>1|<f7>0|<f8>0|<f9>0|<f10>1|<f11>0|<f12>0|<f13>0|<f14>0|<f15>0|<f16>0|..."] num [label="2^-8"] BF16:f0->Sign BF16:f1->Exp BF16:f8->Exp BF16:f9->Fraction BF16:f15->Fraction BF16:f16->num } ``` * 最後利用BB2 = `(e) 0xffff0000` 截斷後方 `16` 個 Bits ($2^{-8}$ 至 $2^{-23}$) ## 2. ```cpp #define RINGBUF_DECL(T, NAME) \ typedef struct { \ int size; \ int start, end; \ T *elements; \ } NAME #define RINGBUF_INIT(BUF, S, T) \ { \ static T static_ringbuf_mem[S + 1]; \ BUF.elements = static_ringbuf_mem; \ } \ BUF.size = S; \ BUF.start = 0; \ BUF.end = 0; #define NEXT_START_INDEX(BUF) \ (((BUF)->start != (BUF)->size) ? ((BUF)->start + RB1) : 0) #define NEXT_END_INDEX(BUF) (((BUF)->end != (BUF)->size) ? ((BUF)->end + RB2) : 0) #define is_ringbuf_empty(BUF) ((BUF)->end == (BUF)->start) #define is_ringbuf_full(BUF) (NEXT_END_INDEX(BUF) == (BUF)->start) #define ringbuf_write_peek(BUF) (BUF)->elements[(BUF)->end] #define ringbuf_write_skip(BUF) \ do { \ (BUF)->end = NEXT_END_INDEX(BUF); \ if (is_ringbuf_empty(BUF)) \ (BUF)->start = NEXT_START_INDEX(BUF); \ } while (0) #define ringbuf_read_peek(BUF) (BUF)->elements[(BUF)->start] #define ringbuf_read_skip(BUF) (BUF)->start = NEXT_START_INDEX(BUF); #define ringbuf_write(BUF, ELEMENT) \ do { \ ringbuf_write_peek(BUF) = ELEMENT; \ ringbuf_write_skip(BUF); \ } while (0) #define ringbuf_read(BUF, ELEMENT) \ do { \ ELEMENT = ringbuf_read_peek(BUF); \ ringbuf_read_skip(BUF); \ } while (0) ``` [do...while(0)的妙用](https://kezeodsnx.pixnet.net/blog/post/28380463) * 首先看到 `RINGBUF_DECL` 與 `RINGBUF_INIT`,宣告了一個 `RINGBUF` 結構並在 `RINGBUF_INIT` 內建立輸入 `S + 1` 大小的陣列,將`start` 與 `end` 標記元素 `0` * 接著看到 `ringbuf_write`,呼叫了 `ringbuf_write_peek` 與 `ringbuf_write_skip` * `ringbuf_write_peek` 將元素存入 `(BUF)->elements[(BUF)->start]` * `ringbuf_write_skip` 則用來判斷 `start` 與 `end` 元素位置的更動 ```graphviz digraph SR{ node[shape=record] BF16 [label="<f0>5|<f1>..."] s [label="start"] e [label="end"] s->BF16:f0 e->BF16:f0 BF16_d [label="<f0>|<f1>|..."] s_d [label="start"] e_d [label="end"] s_d->BF16_d:f0 e_d->BF16_d:f1 } ``` 但若 `S = 2` 陣列則為`3`,此時寫入 `3` ```graphviz digraph SR{ node[shape=record] BF16 [label="<f0>5|<f1>4|<f2>"] s [label="start"] e [label="end"] s->BF16:f0 e->BF16:f2 BF16_d [label="<f0>5|<f1>4|<f2>3"] s_d [label="start"] e_d [label="end"] s_d->BF16_d:f0 e_d->BF16_d:f0 BF16_t [label="<f0>5|<f1>4|<f2>3"] s_t [label="start"] e_t [label="end"] s_t->BF16_t:f1 e_t->BF16_t:f0 } ``` 因此可以不斷循環寫入,覆蓋先前的資料 * 所以RB1 = `(a) 1`, RB2 = `(a) 1` ## 3. 考慮到以下靜態初始化的 singly-linked list 實作: ```cpp #include <stdio.h> /* clang-format off */ #define cons(x, y) (struct llist[]){{y, x}} /* clang-format on */ struct llist { int val; struct llist *next; }; void sorted_insert(struct llist **head, struct llist *node) { if (!*head || (*head)->val >= node->val) { SS1; SS2; return; } struct llist *current = *head; while (current->next && current->next->val < node->val) current = current->next; node->next = current->next; current->next = node; } void sort(struct llist **head) { struct llist *sorted = NULL; for (struct llist *current = *head; current;) { struct llist *next = current->next; sorted_insert(&sorted, current); current = next; } *head = sorted; } int main() { struct llist *list = cons(cons(cons(cons(NULL, A), B), C), D); struct llist *p; for (p = list; p; p = p->next) printf("%d", p->val); printf("\n"); sort(&list); for (p = list; p; p = p->next) printf("%d", p->val); printf("\n"); return 0; } ``` 執行結果為: ``` 9547 4579 ``` * 這題在實作靜態初始化的 `link list`,首先看到 `#define cons(x, y) (struct llist[]){{y, x}}` 與 `struct llist *list = cons(cons(cons(cons(NULL, A), B), C), D)` 這邊偷偷在 `define` 內調換了 `x`, `y` 的位置,所以 `*next = x`, `val = y`,所以其實結構是 `D->C->B->A->NULL` ,因此 `D = (a) 9` `C = (b) 5` `B = (c) 4` `A = (d) 7` * 接著看到 `sort` 與 `sorted_insert`,從執行結果我們可以確定這是一個 `increasing order` 的 `sorting`,`!*head || (*head)->val >= node->val` 判斷當 `sorted` 為空或 `head` 的數值大於當前 `node` 時,將 `node` 插入 `head` 因此 `SS1 = (d) node->next = *head` `SS2 = (b) *head = node` * 而下面部分則在做剩下的判斷,如果 `node` 大於 `head`,則繼續做由小到大的 `insertion sort` ## 4. LeetCode 287. [Find the Duplicate Number](https://leetcode.com/problems/find-the-duplicate-number/) 給定一個整數序列,其中會有一個數值重複,請找出。 已知條件: 1. 假設陣列長度為 $n$,數值的範圍是 $1$ 到 $n−1$ 1. 重複的數值不一定只重複一次 考慮以下程式碼是可能的解法: ```cpp int findDuplicate(int *nums, int numsSize) { int res = 0; const size_t log_2 = 8 * sizeof(int) - __builtin_clz(numsSize); for (size_t i = 0; i < log_2; i++) { int bit = 1 << i; int c1 = 0, c2 = 0; for (size_t k = 0; k < numsSize; k++) { if (k & bit) ++c1; if (nums[k] & bit) ++c2; } if (CCC) res += bit; } return res; } ``` `log_2 = 8 * sizeof(int) - __builtin_clz(numsSize)` 利用 `bit` 位移逐個 `bit` 做檢查並利用 * `c1` 紀錄給定的陣列目錄該 `bit` 有幾個 `1` * `c2` 紀錄給定的陣列內的數值該 `bit` 有幾個 `1` 因此,若重複的數在該 `bit` 為 `0` 則 `c1 >= c2`,若重複的數在該 `bit` 為 `1` 則 `c1 < c2` 所以 `CCC = (b) c1 < c2` * 舉例來說,`nums = [3, 1, 3, 2]` | num | binary | | ----- | --- | | 0 | 000 | | 1 | 001 | | 2 | 010 | | 3 | 011 | | c1 | 022 | | num | binary | | ----- | --- | | 3 | 011 | | 1 | 001 | | 3 | 011 | | 2 | 010 | | c2 | 033 | 走訪全數陣列成員後,$res = 2^0+2^1 = 3$ ### 延伸問題 ```cpp int findDuplicate(int* nums, int numsSize){ bool list[numsSize]; memset(list, 0, sizeof(list)); for(int i = 0; i < numsSize; i++){ list[nums[i]] ^= 1; if(!list[nums[i]]) return nums[i]; } return -1; } ``` ![](https://i.imgur.com/1g86qYV.png) 透過 `bool list[numsSize]` 建立一個 `Flags arrary`,當重複的數出現時 `trigger`,算是比較直觀簡單的做法。