ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

第 2 篇:数字在内存里怎么排队(零门槛入门系列)

第 2 篇:数字在内存里怎么排队(零门槛入门系列) 上一篇第 1 篇《推理引擎到底在干什么》 下一篇第 3 篇《权重文件的解剖》一句话导读机器里没有表格只有一条长长的队伍形状是你和它约好的切法第几行第几列只是一个乘法。关键词矩阵、行主序、列主序、形状、下标换算上一篇把生成循环的四步摆清楚了其中算一遍的原料是一大堆数字。可机器里并没有一叠纸只有一条能存东西的长走廊。这一篇就解决一个问题这些数字在内存里到底怎么排队第几行第几列又凭什么能被机器算出来。① 一句话概念矩阵在内存里是一条线形状只是怎么切的约定。② 起点一条又长又直的储物柜走廊想象一条很长很长的储物柜走廊柜子一个挨着一个从 0 号开始编号一直排下去。走廊只有一条柜子的先后顺序永远不变。现在有人递给你一张表格说这是2 行 3 列要你把里面的东西依次放进柜子。你没法把表格挂在墙上只能一格一格地往柜子里塞。于是你定个规矩先放第一行的 3 格再放第二行的 3 格。规矩定完表格就消失了剩下的只是一个 6 格的长条。以后别人问第二行第三列在哪你不需要真有一张表只要按规矩算一下该去第几号柜子。整排柜子从头到尾没变过变的只是你们约好的切法。而切法不同问同一个格子的代价也不同有的问法是一路顺着走有的问法要来回跳着走。③ 伪代码两种切法两条线矩阵 A 的形状 (2 行, 3 列) # 形状只是约定本身不占地方 线 把 A 一行接一行拉直 # 得到 6 个格子线[0] .. 线[5] 函数 取(A, i, j) - 数: # i 是第几行j 是第几列 → 线[i * 3 j] # 第几行第几列变成了一个乘法 按行拉直: 线[i * 3 j] # 行主序 按列拉直: 线[j * 2 i] # 列主序先列后行 # 代价: 取一个数 O(1)但按什么顺序取决定了要跳几次同一个矩阵可以拉成两条不同的线如图 1 所示。图 1矩阵到一维两种排法左边是那个逻辑形状2 行 3 列两行标着行 0 / 行 1、三列标着列 0~列 2格内写的是A00~A12右侧上下并排两条 6 格带——上面一条标题是按行拉直行主序一行接一行格内依次是A00 A01 A02 A10 A11 A12下面一条标题是按列拉直列主序一列接一列格内依次是A00 A10 A01 A11 A02 A12左侧矩阵到这两条带各连着一条虚线箭头。底部方框给出换算取 A[i][j] - 线[i × 3 j]并写明沿列读取行主序要跳 3 次列主序一次都不用跳以及引擎宁可在转换期就把数据排好见第 16 篇。逐行要点矩阵 A 的形状 (2 行, 3 列)形状shape是给人看的约定。机器里没有二维结构只有那条线几行几列是你们俩说好的切法。把 A 一行接一行拉直这就是行主序row-major——同一行的数挨在一起行与行首尾相接。它的好处是按行扫一圈时一路顺读。线[i * 3 j]这是全篇最该记住的一行。你要的第 i 行第 j 列在机器眼里只是先跳过 i 整行每行 3 个再加上 j。乘的那个 3 就是每行的长度。按列拉直同样一条线换一种约定就得到列主序column-major。列主序里按列扫一圈才顺读而按行扫一圈反而要跳——两种排法没有谁更对只有谁更配你的读法。对应式子线[j * 2 i]那个 2 就是每列的元素个数。代价: 取一个数 O(1)换算本身不贵贵的是顺着读还是跳着读。机器喜欢顺着读跳着读会把整条线的旅行变成来回折返。这就是快慢的第一个来源。④ 纸笔实验必做设定矩阵如下先按行、再按列各拉成一条线然后模拟沿列读取一列一列地读。矩阵 A 1 2 3 4 5 6第一步写出两条线按行拉直行主序1 2 3 4 5 6格号 0~5按列拉直列主序1 4 2 5 3 6格号 0~5第二步沿列读取数跳跃次数约定一次跳跃 在同一列里续读时下一格不是紧挨着的那一格换列不计反过来沿行读取时按同一行里续读来数换行不计。在行主序线上沿列读第 1 列要先读格 0值 1再读同一列下一行的值 4——可 4 在格 3中间隔了 2 格这算 1 次跳跃。三列各跳 1 次合计3 次跳跃REPL 版里换第 2 列演示结论一样三列各跳 1 次。在列主序线上沿列读第 1 列的值 1、4 落在格 0、1紧挨着第二列 2、5 落在格 2、3第三列 3、6 落在格 4、5。全程0 次跳跃。预期结果读法行主序线列主序线沿行读取0 次跳跃4 次跳跃沿列读取3 次跳跃0 次跳跃结论数据没变、数量没变只因为约好的切法和实际的读法对不上多出来的就是这 3 次跳跃。现实里矩阵远不止 2×3跳跃次数按行列规模成倍放大——所以工程上第一件经常做的事就是让切法配上读法。这一点在第 16 篇在转换期就把数据排好里还会再遇到。可选 REPL 版任意语言直接跑矩阵 [[1, 2, 3], [4, 5, 6]] 行主序 [x 对每个 行 于 矩阵 对每个 x 于 行] # [1, 2, 3, 4, 5, 6] 列主序 [矩阵[i][j] 对每个 j 于 范围(3) 对每个 i 于 范围(2)] # [1, 4, 2, 5, 3, 6] 对每个 线 于 [行主序, 列主序]: 位置 [线.第几格(矩阵[i][1]) 对每个 i 于 范围(2)] # 第 2 列两个值在线上第几格 打印(线, 位置, 隔了, 位置[1] - 位置[0] - 1, 格)预期输出两行[1,2,3,4,5,6] [1, 4] 隔了 2 格与[1,4,2,5,3,6] [2, 3] 隔了 0 格。⑤ 进阶锚点进阶锚点本篇概念在《30 天手搓推理引擎》Day 2里被真正实现——2-1 _Static_assert让编译器守卫你的内存布局/2-2 平台层一个头文件守住全部平台契约vllm_platform.h/2-3 设备画像识别 RK3588而不是猜vllm_device.c。入门版到这里就够了进阶版会多出一道自动检查谁改错一个字段程序当场拒绝往下走、逐字节核对的读写纪律、以及在真实设备上读出自己是谁而不是靠猜。想动手 → 仓库https://gitee.com/pei-xiaoguang/kestrel-llm从 Day 1 开始。本篇里形状只是约定这句话在进阶 Day 2 会变成几行代码约定一改程序立刻报错。下篇预告既然数字是一条线那么几百万条线是怎么装进一个文件、又怎么被用起来的第 3 篇给权重文件做一次解剖。
返回列表