diff --git a/_drafts/2023-08-15-streamlit-tutorial.md b/_drafts/2023-08-15-streamlit-tutorial.md index 29c1ae0..015eb7f 100644 --- a/_drafts/2023-08-15-streamlit-tutorial.md +++ b/_drafts/2023-08-15-streamlit-tutorial.md @@ -381,3 +381,76 @@ print("end", rerun_id) 参考资料: - 博客: [part-1](https://blog.streamlit.io/streamlit-authenticator-part-1-adding-an-authentication-component-to-your-app/), [part-2](https://blog.streamlit.io/streamlit-authenticator-part-2-adding-advanced-features-to-your-authentication-component/) + + +## 示例 + +### 常见错误: `session_state` 无法被修改 + +```python +import streamlit as st +from uuid import uuid4 + +RUN_ID = str(uuid4()) +print(f"RERUN {RUN_ID}: {st.session_state}") +st.text_input("text", key="text") +button = st.button("save") +if button: + print(f"点击按钮 {RUN_ID}: {st.session_state}") + st.session_state["text"] = "" +print(f"FINISH_RUN {RUN_ID}: {st.session_state}") +``` + +将上述代码运行起来后, 与前端交互, 先在文本输入框里输入, 一切正常, 点击按钮, 则执行报错, 后端日志如下 + +``` +# 第一次 rerun +RERUN bd8c35e4-60bc-4a62-b741-c1503ee37b86: {} +FINISH_RUN bd8c35e4-60bc-4a62-b741-c1503ee37b86: {'text': ''} + +# 输入文本后触发 rerun +RERUN cfd7307c-6bd1-4368-b155-a9867f0d2d15: {'text': '12'} +FINISH_RUN cfd7307c-6bd1-4368-b155-a9867f0d2d15: {'text': '12'} + +# 点击按钮后触发 rerun +RERUN 17935c8e-78af-4fa3-b28a-f807a571b21d: {'text': '12'} +点击按钮 17935c8e-78af-4fa3-b28a-f807a571b21d: {'text': '12'} +streamlit.errors.StreamlitAPIException: `st.session_state.text` cannot be modified after the widget with key `text` is instantiated. +``` + +原因是 streamlit 在组件被渲染了之后就不允许修改: 在我们点击按钮时, streamlit 会记录组件的现状, 并重新运行脚本, 当我们进入 if 分支后, 尝试对组件的值重新修改, 就会引发错误. + +**修正方式** + +```python +import streamlit as st +from uuid import uuid4 + +RUN_ID = str(uuid4()) +print(f"RERUN {RUN_ID}: {st.session_state}") + +def button_callback(): + print(f"点击按钮 {RUN_ID}: {st.session_state}") + st.session_state["text"] = "" + +st.text_input("text", key="text") +button = st.button("save", on_click=button_callback) +print(f"FINISH_RUN {RUN_ID}: {st.session_state}") +``` + +后端日志 + +``` +# 第一次 rerun +RERUN 274c2950-a3ad-4a15-b992-7291958d3cfa: {} +FINISH_RUN 274c2950-a3ad-4a15-b992-7291958d3cfa: {'text': ''} + +# 输入文本后触发 rerun +RERUN 1e371487-47cb-4daf-8e04-cfef94b09800: {'text': '12'} +FINISH_RUN 1e371487-47cb-4daf-8e04-cfef94b09800: {'text': '12'} + +# 点击按钮后, 注意: 在 callback 里, RUN_ID 的值还是上一次的值, 但 rerun 之后, RUN_ID 被更新了 +点击按钮 1e371487-47cb-4daf-8e04-cfef94b09800: {'text': '12'} +RERUN c62c4d41-213d-4d9f-aa6d-d37597360f3d: {'text': ''} +FINISH_RUN c62c4d41-213d-4d9f-aa6d-d37597360f3d: {'text': ''} +``` \ No newline at end of file diff --git a/_drafts/2024-10-09-gguf.md b/_drafts/2024-10-09-gguf.md index 2b4692d..5f10175 100644 --- a/_drafts/2024-10-09-gguf.md +++ b/_drafts/2024-10-09-gguf.md @@ -1146,25 +1146,25 @@ def my_dequant(blocks): b48_64_3 = b_3[48:64] result = np.array([ - c[0] * ((b0_16_0<<4)+a0_16_low-32), - c[1] * ((b16_32_0<<4)+a16_32_low-32), - c[2] * ((b0_16_1<<4)+a32_48_low-32), - c[3] * ((b16_32_1<<4)+a48_64_low-32), + c[0] * ((b0_16_0<<4)+a0_16_low-32).view(np.int8), + c[1] * ((b16_32_0<<4)+a16_32_low-32).view(np.int8), + c[2] * ((b0_16_1<<4)+a32_48_low-32).view(np.int8), + c[3] * ((b16_32_1<<4)+a48_64_low-32).view(np.int8), - c[4] * ((b0_16_2<<4)+a0_16_high-32), - c[5] * ((b16_32_2<<4)+a16_32_high-32), - c[6] * ((b0_16_3<<4)+a32_48_high-32), - c[7] * ((b16_32_3<<4)+a48_64_high-32), - - c[8] * ((b32_48_0<<4)+a64_80_low-32), - c[9] * ((b48_64_0<<4)+a80_96_low-32), - c[10] * ((b32_48_1<<4)+a96_112_low-32), - c[11] * ((b48_64_1<<4)+a112_128_low-32), - - c[12] * ((b32_48_2<<4)+a64_80_high-32), - c[13] * ((b48_64_2<<4)+a80_96_high-32), - c[14] * ((b32_48_3<<4)+a96_112_high-32), - c[15] * ((b48_64_3<<4)+a112_128_high-32), + c[4] * ((b0_16_2<<4)+a0_16_high-32).view(np.int8), + c[5] * ((b16_32_2<<4)+a16_32_high-32).view(np.int8), + c[6] * ((b0_16_3<<4)+a32_48_high-32).view(np.int8), + c[7] * ((b16_32_3<<4)+a48_64_high-32).view(np.int8), + + c[8] * ((b32_48_0<<4)+a64_80_low-32).view(np.int8), + c[9] * ((b48_64_0<<4)+a80_96_low-32).view(np.int8), + c[10] * ((b32_48_1<<4)+a96_112_low-32).view(np.int8), + c[11] * ((b48_64_1<<4)+a112_128_low-32).view(np.int8), + + c[12] * ((b32_48_2<<4)+a64_80_high-32).view(np.int8), + c[13] * ((b48_64_2<<4)+a80_96_high-32).view(np.int8), + c[14] * ((b32_48_3<<4)+a96_112_high-32).view(np.int8), + c[15] * ((b48_64_3<<4)+a112_128_high-32).view(np.int8), ]) * d return result @@ -1246,7 +1246,38 @@ float make_qx_quants(int n, int nmax, const float * x, int8_t * L, int rmse_type mkdir build && cd build && cmake ../ && cmake --build . && ./bin/learn ``` -ps: 计划给 llama.cpp 提 PR, 增加 Q6_K 的量化算法, 需要对齐 C 的实现 +任务 2: + +在 `ggml/src/ggml-quants.h` 中, 有如下关于 `q6_K` 的声明 + +```c +void quantize_row_q6_K_ref(const float * GGML_RESTRICT x, block_q6_K * GGML_RESTRICT y, int64_t k); + +void quantize_row_q6_K(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k); + +void dequantize_row_q6_K(const block_q6_K * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k); + +size_t quantize_q6_K(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix); +``` + +而在 `ggml/src/ggml-quants.c` 中, 除上面以外, 还包含: + +```c +static void quantize_row_q6_K_impl(const float * restrict x, block_q6_K * restrict y, int64_t n_per_row, const float * quant_weights); +``` + +quantize 相关的 4 个函数调用关系如下: + +- `quantize_q6_K` 条件调用 `quantize_row_q6_K_ref` 或 `quantize_row_q6_K_impl` +- `quantize_row_q6_K` 调用 `quantize_row_q6_K_ref` +- `quantize_row_q6_K_ref` 不调用其他 +- `quantize_row_q6_K_impl` 不调用其他 + +从函数入参来看 `quantize_q6_K` 和 `quantize_row_q6_K` 的参数是基本类型的指针, 而 `quantize_row_q6_K_ref` 和 `quantize_row_q6_K_impl` 包含了自定义数据结构 `block_q6_K` + + + +任务 4 原始的 [PR](https://github.com/ggerganov/llama.cpp/commit/4134999e01f31256b15342b41c4de9e2477c4a6c#diff-ce8ce4f4e23f467e525c2192d310d1530528facef89eafeedef4cb5574097d65R230), 对齐的验证方法如下(TODO:有些不优雅: `make libglmm.so`): diff --git a/_drafts/2024-11-14-numeric-representation.md b/_drafts/2024-11-14-numeric-representation.md new file mode 100644 index 0000000..6327919 --- /dev/null +++ b/_drafts/2024-11-14-numeric-representation.md @@ -0,0 +1,84 @@ +--- +layout: post +title: "(P0) 数值表示" +date: 2024-11-14 13:00:04 +0800 +labels: [ieee754] +--- + + +## 动机、参考资料、涉及内容 + +数值表示(整数,浮点数,大/小端序,字节对齐), 一些 Python/C++/Numpy 中关于数值类型的常用手段 + +## IEEE 754 + +IEEE 754 表示法的描述参见 CSAPP, 简要描述如下: + +浮点数的表示方法为:$V=(-1)^s\times M \times 2^E$, 其中 $M$ 表示 $[0, 2-\epsilon]$ 中的一个数, $E$ 表示一个整数, $s$ 表示 0(正数) 或 1(负数)。 + +以 float32 为例, 具体的编码方式为: + +- 第 1 位表示符号 +- 接下来的 $k=8$ 位 $exp = e_7e_6...e_0$ 表示指数 $E$ (exponent) +- 最后的 $n=23$ 位 $frac = f_{22}f_{21}f_0$ 表示系数 $M$ (significand) + +具体的编码方式如下, 分为 3 类情形: + +**1. Normalized values** + +当指数位不为全0或全1时,属于此类。这种情况下,$E=exp-Bias$,$M=1+0.f_{23}f_{22}...f_{0}$。其中 $Bias=2^{k-1}-1=2^7-1=127$。 + +**2. Denormalized values** + +当指数位全为0时,属于此类。这种情况下,$E=1-Bias=-126$, $M=0.f_{23}f_{22}...f_{0}$。 + +这类编码方式主要有两个目的:一是可以表示出0:+0.0被编码为全0, -0.0被编码为第一位是1,其余位全为0;二是可以表示数十分接近0的数字 + +**3. Special values** + +当指数位全为1时,属于此类。当 $frac$ 全为 0 时,代表 $-\inf$ (如果符号位为1) 或 $+\inf$ (如果符号位为1);如果 $frac$ 不全为0,则代表 $NaN$ + +**总结** + +从表示范围来看,三类值如下分布(一个数的正数表示与负数表示只相差符号位) + +正数: +- +0.0 (denormalized): $s=0$, $exp=00000000$, $frac=00...00$ +- 最小正数 (denormalized): $\epsilon=2^{-2^{k-1}+2-n}=2^{-126-23}$, $s=0$, $exp=00000000$, $frac=00...01$ +- ... (denormalized) +- 最大 denormalized 正数 (denormalized): $2^{-126} - \epsilon$, $s=0$, $exp=00000000$, $frac=11...11$ +- 最小 normalized 正数 (normalized): $2^{-2^{k-1}+2}=2^{-126}$, $s=0$, $exp=00000001$, $frac=00...00$ +- ... (normalized) +- 最大正数 (normalized): $(2-2^{-n})\times2^{(2^{k-1}-1)}=(2-2^{-23}) \times 2^{127}$, $s=0$, $exp=11111110$, $frac=11...11$ +- 正无穷 (special): $s=0$, $exp=11111111$, $frac=00...00$ +- NaN: $s=0$, $exp=11111111$, $frac\neq 00...00$ + +这样我们知道: + +- fp64: $k=11$, $n=52$, 最大正数为 $2^{1024}-2^{971}$, 最小正数为 $2^{-1074}$ +- fp32: $k=8$, $n=23$, 最大正数为 $2^{128}-2^{104}$, 最小正数为 $2^{-149}$ +- fp16: $k=5$, $n=10$, 最大正数为 $2^{16}-2^3=65536-32=65504$, 最小正数为 $2^{-24}$ + +例子: +``` +1的表示: (1+0.0)*2^0: 0 01111111 000...000 +2的表示: (1+0.0)*2^1: 0 10000000 000...000 +5/2的表示: (1+1/4)*2^1: 0 10000000 010...000 +``` + +## np.frombuffer + +```python +np.frombuffer(b"\x00\x00\x80\x3f\x00\x00\x20\x40", np.float32) # [1.0, 2.5] + +# 每4位为一组: +# \x00: 00000000, \x00: 00000000, \x80: 10000000, \x3f: 00111111 +# 倒序拼接: +# 00111111 10000000 00000000 00000000 +# 然后解码为fp32: 1.0 + +# \x00: 00000000, \x00: 00000000, \x20: 00100000, \x40: 01000000 +# 倒序拼接: +# 01000000 00100000 00000000 00000000 +# 然后解码为fp32: 2.5 +``` \ No newline at end of file