Llama大模型运行的消费级硬件要求【CPU|GPU|RAM|SSD】

大型语言模型 (LLM) 是强大的工具,可以为各种任务和领域生成自然语言文本。 最先进的LLM之一是 LLaMA(大型语言模型 Meta AI),这是由 Facebook 的研究部门 Meta AI 开发的一个包含 650 亿个参数的模型。

要在家运行 LLaMA 模型,你需要一台配备强大 GPU 的计算机,能够处理推理所需的大量数据和计算。 在本文中,我们将讨论本地运行 LLaMA 的一些硬件要求。

在消费类硬件上运行 LLaMA 模型有多种不同的方法。 最常见的方法是使用单个 NVIDIA GeForce RTX 3090 GPU。 该 GPU 具有 24 GB 内存,足以运行 LLaMA 模型。 RTX 3090 可以运行 4 位量化的 LLaMA 30B 模型,每秒大约 4 到 10 个令牌。 24GB VRAM 似乎是在消费类台式电脑上使用单个 GPU 的最佳选择。

但是,如果你想运行更大的模型,则必须使用双 GPU 设置。 这将允许你将模型权重放入 VRAM 中。 你还可以使用高级 GPU,例如 NVIDIA A100。 这个GPU非常昂贵,但有40GB内存,可以更好地运行模型。

你还可以在 CPU 上运行 LLaMA 模型。 必须使用模型的 GGML 版本(LLaMA、Vicuna、Alpaca 和 GPT4All)以及名为 llama.cpp 的软件才能使用CPU。 运行 LLaMA 的合适 CPU 是 Core i7 12900K 和 Ryzen 9 5900X。 有关此主题的更多信息,请查看 CPU 部分。

请记住,训练或微调 LLaMA 模型需要比运行模型更多的 VRAM。 这是因为训练过程需要将模型以及训练数据存储在 VRAM 中。 训练所需的 VRAM 量取决于模型的大小和训练数据量。

为了在台式电脑上使用 LLaMA 模型,请查看需要满足的一些硬件要求:

7-19更新:LLAMA  2 权威指南
7-30更新:LLAMA 2本地运行的3个方案

1、运行 LLaMA 的 GPU要求

在消费级机器上运行 LLaMA 时,GPU 是最重要的计算机硬件,因为它负责运行模型所需的大部分处理。 GPU的性能将直接影响推理的速度和准确性。

模型的不同变体和实现可能需要功能较弱的硬件。 不过,GPU 仍将是系统中最重要的部分。

4 位量化 LLaMA 模型的 GPU 要求:

LLaMA Model Minimum VRAM Requirement Recommended GPU Examples
LLaMA-7B 6GB RTX 3060, GTX 1660, 2060, AMD 5700 XT, RTX 3050
LLaMA-13B 10GB AMD 6900 XT, RTX 2060 12GB, 3060 12GB, 3080, A2000
LLaMA-30B 20GB RTX 3080 20GB, A4500, A5000, 3090, 4090, 6000, Tesla V100, Tesla P40
LLaMA-65B 40GB A100 40GB, 2x3090, 2x4090, A40, RTX A6000, 8000
  • LLama-7B

为了有效运行 LLaMA-7B,建议使用至少具有 6GB VRAM 的 GPU。 适合此模型的 GPU 示例是 RTX 3060,它提供 8GB VRAM 版本。 其他 GPU(例如 GTX 1660、2060、AMD 5700 XT 或 RTX 3050)也具有 6GB VRAM,可以作为支持 LLaMA-7B 的良好选择。

  • LLaMA-13B

为了获得 LLaMA-13B 的最佳性能,建议使用至少具有 10GB VRAM 的 GPU。 满足此要求的 GPU 示例包括 AMD 6900 XT、RTX 2060 12GB、3060 12GB、3080 或 A2000。 这些 GPU 提供必要的 VRAM 容量来有效处理 LLaMA-13B 的计算需求。

  • LLaMA-30B

为确保 LLaMA-30B 顺利运行,建议使用至少 20GB VRAM 的 GPU。 RTX 3080 20GB、A4500、A5000、3090、4090、6000 或 Tesla V100 是提供所需 VRAM 容量的 GPU 示例。 这些 GPU 可实现 LLaMA-30B 的高效处理和内存管理。

  • LLaMA-65B

LLaMA-65B 与至少具有 40GB VRAM 的 GPU 配合使用时,性能最佳。 适用于此型号的 GPU 示例包括 A100 40GB、2x3090、2x4090、A40、RTX A6000 或 8000。这些 GPU 提供充足的 VRAM 容量来处理与 LLaMA-65B 相关的密集计算任务。

每个 LLaMA 模型都有特定的 VRAM 要求,建议的 GPU 是根据其满足或超过这些要求的能力来选择的,以确保相应的 LLaMA 模型平稳高效的性能。

2、运行LLaMA 的 CPU要求

除了 GPU 之外,你还需要一个可以支持 GPU 并处理其他任务(例如数据加载和预处理)的 CPU。 基于 GPQT (GPU) 的模型对 CPU 的要求低于针对 CPU 优化的模型。

适合 LLaMA 的 CPU 是 Intel Core i9-10900K、i7-12700K 或 Ryzen 9 5900x。 但是,为了获得更好的性能,你可能需要使用更强大的 CPU,例如具有 64 核和 128 线程的 AMD Ryzen Threadripper 3990X。 最后,真正重要的是 CPU 的速度。 这才是真正的力量所在。 当在昂贵的服务器 CPU 和高端游戏 CPU 之间进行选择时,后者占据主导地位。

我们必须注意,本文讨论的模型是针对 GPU 的,但也有针对 CPU 的 LLaMa 模型优化器。 例如,GGML 是一种解决方案,可以解决处理大型模型时 GPU 内存带来的限制。 如果你更喜欢使用 CPU,建议运行 GGML 格式的模型文件。

你可以使用名为 llama.cpp(LLaMA 模型的接口)的软件来利用你的 CPU。 llama.cpp 最近的更新引入了新的增强功能,使用户能够在 CPU 和 GPU 之间分配模型的工作负载。 这不仅有利于加载更大的模型,而且还提高了令牌的速度。

这是使用 Ryzen 7 3700X 和 128GB RAM 运行 llama.cpp 的示例。

GGML Model Memory per Token Load Time Sample Time Predict Time Total Time
LLaMA-7B 4-bit 14434244 bytes 1270.15 ms 325.76 ms 15147.15 ms / 117.42 ms per token 17077.88 ms
LLaMA-13B 4-bit 22439492 bytes 2946.00 ms 86.11 ms 7358.48 ms / 216.43 ms per token 11019.28 ms
LLaMA-30B 4-bit 43387780 bytes 6666.53 ms 332.71 ms 68779.27 ms / 533.17 ms per token 77333.97 ms
LLaMA-65B 4-bit 70897348 bytes 14010.35 ms 335.09 ms 140527.48 ms / 1089.36 ms per token 157951.48 ms

3、运行LLaMA 的内存要求

除了GPU和CPU之外,你还需要足够的RAM(随机存取存储器)和存储空间来存储模型参数和数据。 4 位 LLaMA-30B 的最低 RAM 要求为 32 GB,可以将整个模型保存在内存中,而无需交换到磁盘。 但是,对于较大的数据集或较长的文本,你可能需要使用更多 RAM,例如 64 GB 或 128 GB。

CPU 和内存之间的带宽是一个关键因素,我想强调它的重要性。 当生成单个 token 时,整个模型需要从内存中读取一次。 假设你有 Core i9-10900X(4 通道支持)和 DDR4-3600 内存,这意味着吞吐量为 115 GB/s,而你的型号大小为 13 GB。 在这种情况下,理论限制约为每秒 8.8 个令牌,无论你的 CPU 有多快或有多少个并行核心。

RAM 的大小取决于 GGML 量化的类型和你使用的模型(LLaMA、Alpaca、Wizard、Vicuna 等)。

这些是 在CPU上使用 LLaMA 模型的内存 (RAM) 要求:

GGML Model Original size Quantized size (4-bit) Quantized size (5-bit) Quantized size (8-bit)
7B 13 GB 3.9 – 7.5 GB 7.5 – 8.5 GB 8.5 – 10.0 GB
13B 24 GB 7.8 – 11 GB 11.5 – 13.5 GB 13.5 – 17.5 GB
30B 60 GB 19.5 – 23.0 GB 23.5 – 27.5 GB 28.5 – 38.5 GB
65B 120 GB 38.5 – 47.0 GB 47.0 – 52.0 GB 71.0 – 80.0 GB

在 CPU 上运行时基于内存 (RAM) 速度的模型 (8GB) 推理速度:

RAM speed CPU CPU channels Bandwidth *Inference
DDR4-3600 Ryzen 5 3600 2 56 GB/s 7 tokens/s
DDR4-3200 Ryzen 5 5600X 2 51 GB/s 6.3 tokens/s
DDR5-5600 Core i9-13900K 2 89.6 GB/s 11.2 tokens/s
DDR4-2666 Core i5-10400f 2 41.6 GB/s 5.1 tokens/s

速度为理论最大值,取决于操作系统和系统负载。

4、运行LLaMA的存储要求

LLaMA的最低存储要求是1TB NVMe SSD,可以存储模型文件和数据文件,读写速度很快。 但是,为了更多数据或备份目的,你可能需要使用更多存储空间,例如 2 TB 或 4 TB SSD。

选择高速存储。 选择具有出色顺序速度的 PCIe 4.0 NVMe SSD,以促进存储和系统 RAM 之间的快速数据传输。

5、模型量化如何影响 GPU 的选择?

量化 LLM使用更少的位数来存储和处理模型的权重和激活。 这使得它们的 GPU 部署更快、更高效。

4 位量化 LLM 每个权重或激活仅使用 4 位。 这意味着它们比全精度模型占用更少的内存和计算时间。 它们可以在 VRAM 容量较低的 GPU 上平稳运行。

8 位量化 LLM 每个权重或激活使用 8 位。 与全精度模型相比,这仍然减少了内存和计算成本,但不如 4 位量化那么多。 它们需要更多的 GPU 内存和计算能力才能良好运行。 它们更适合具有高 VRAM 容量和计算能力的 GPU。

总而言之,4 位量化 LLM 效率更高,并且可以在 VRAM 容量较低的 GPU 上运行。 8 位量化 LLM 的效率稍低,需要具有高 VRAM 容量和计算能力的 GPU。

LLaMA Precision GPU Memory Requirements Computational Demands Suitable GPU
Native (32-bit) Higher requirements Higher computational demands GPUs with larger VRAM capacities and high computational capabilities
16-bit Quantized Moderate requirements Moderate computational demands GPUs with moderate VRAM capacities and good computational capabilities
8-bit Quantized Relatively higher requirements Slightly higher computational demands GPUs with larger VRAM capacities and higher computational capabilities
4-bit Quantized Lower requirements Lower computational demands GPUs with limited VRAM capacities

正如你所看到的,LLaMA 的精度对其 GPU 内存需求和计算需求有直接影响。 原生(32 位)LLM 需要最多的 GPU 内存和计算能力,而 4 位量化 LLM 需要最少。

适用于 LLaMA 的 GPU 取决于其精度以及您想要使用它执行的特定任务。 如果您需要在各种任务上运行大型 LLaMA,那么您将需要具有大 VRAM 容量和高计算能力的 GPU。 如果您只需要在几个特定任务上运行小型 LLaMA,那么您可以使用具有较小 VRAM 容量和较低计算能力的 GPU。

需要注意的是,随着量化级别的降低,模型的准确性也会降低。 这是因为精度降低可能会导致模型预测出现错误。

最适合你的量化级别取决于你的具体需求和要求。 如果需要一个小而高效的模型,那么你可能需要考虑使用 4 位或 8 位量化模型。 但是,如果你需要高度准确的模型,那么可能需要使用 16 位模型。

6、双GPU是否有效提升 LLaMA性能?

添加第二个 GPU 可能不会像预期那样加快文本生成速度。 瓶颈似乎阻碍了增加更多计算能力的简单解决方案。 一些测试显示出令人惊讶的结果,低端 GPU 每秒生成令牌的速度比高端 GPU 更快。 其原因尚不清楚,文本生成程序可能需要更好的优化才能很好地使用双 GPU 设置。

双 GPU 设置总共具有更多 VRAM,但每个 GPU 仍然有其自己的 VRAM 限制。 30B LLaMA 需要大约 20GB VRAM,因此两个 RTX 3090 GPU(每个都有 24GB VRAM)仍然只有 24GB VRAM 可用。 该模型应适合一个 GPU 的 VRAM 才能正常运行。

但是,如果模型太大而无法容纳单个 GPU 的 VRAM 并且需要利用系统 RAM,则使用多个 GPU 确实可以加快该过程。 在这种情况下,每个 GPU 可以处理模型的一部分,并且计算负载在它们之间分配。 这种并行化可以提高超过单个 GPU 的 VRAM 容量的大型模型的速度。

因此,在处理具有高 VRAM 要求的大型模型时,通常会采用多个 GPU。 它可以有效利用资源并加速训练或推理过程。

将像 65B LLaMA 这样的大型语言模型拆分到具有模型并行性的多个 GPU 上可能会很困难,并且可能会导致通信延迟。 通过 GPU 拆分和同步模型的参数和计算需要仔细编码,并且可能并不总是能大幅提高性能。

双 GPU 设置可能不适用于某些软件。 某些机器学习框架或库可能无法完全使用多个 GPU,并且可能需要额外的工作来设置和优化系统以使用双 GPU。

这些限制意味着,将双 GPU 设置用于 30B LLaMA 的可能优势与难度和潜在问题进行比较非常重要。 有时,获得更强的单GPU或尝试其他优化方法可能是更好的方法。

7、为 LLaMA 选择 PC 硬件的技巧

  • 围绕 GPU 构建

创建一个包含主板、CPU 和 RAM 的平台。 GPU 处理训练和推理,而 CPU、RAM 和存储管理数据加载。 选择支持 PCIe 4.0(或 5.0)、多个 NVMe 驱动器插槽、x16 GPU 插槽和充足内存 DIMM 的主板。 建议使用单线程速度较高的 CPU,例如 Ryzen 5000 或 Intel 第 12/13 代。

  • 型号选择和 VRAM

为了在响应质量方面获得最佳性能,建议在具有至少 20GB VRAM 的 GPU 上运行 8 位 13B 模型或 4 位 30B 模型。 两种型号都提供相似的质量响应,VRAM 可用性应该是决定因素。 投资具有张量核心的 Nvidia GPU 以增强性能。 考虑 RTX 30 系列或 RTX 40 系列等选项,例如 RTX 3090 24GB、RTX 4090 24GB,以获得最佳性能。

  • 速度比较

就每秒生成的令牌而言,13B 模型通常比 30B 模型运行得更快。 虽然确切的速度差异可能有所不同,但与 30B 模型相比,13B 模型往往会在生成速度方面提供显着的改进。

  • 内存要求

目标是至少 1.5 倍 VRAM 容量或两倍 VRAM 以获得最佳性能。 当使用 128GB 或更多 RAM 时,主板和 CPU 的选择变得至关重要。

  • PCIe 4.0 NVMe 固态硬盘

高顺序速度 PCIe 4.0 NVMe SSD 的重要性主要在于将初始模型加载到 VRAM 中。 模型加载后,SSD 对生成速度(令牌/秒)的影响很小。

  • 足够的常规 RAM

拥有足够的常规 RAM(最好是 VRAM 容量的两倍)对于初始模型加载至关重要。 模型一旦加载,对实际生成速度的影响是有限的。 确保初始加载期间有足够的常规 RAM 对于流畅的体验至关重要。

  • CPU单线程速度

CPU 的单线程速度主要对于初始模型加载非常重要,而不是在生成期间运行模型。 CPU的作用在数据预处理、模型加载和其他不依赖GPU的操作等任务中更加突出。

  • 扩展以提高速度

如果你需要将文本生成速度从 15 个令牌/秒提高到 30 个令牌/秒,设置整个 PC 的文字克隆可能比添加第二个 3090 卡更有效。 将整体系统资源(包括 CPU 和 RAM)加倍可能会在提高文本生成速度方面产生更好的结果。

  • 单GPU性能

由于 GPU 本身的内部带宽优势,单个 GPU 通常比多 GPU 设置提供更快的性能。

  • 电源及机箱

投资具有足够容量为所有组件供电的高质量电源。 选择通风良好的宽敞机箱以获得最佳散热效果。

  • DDR5 和未来平台

虽然 DDR5 和 Zen 4 或 AM5 等未来平台具有优势,但稳定性和兼容性可能会有所不同。 考虑投资具有良好 PCIe 插槽布局和内存支持的高端主板,以实现未来的升级。

请记住,虽然这些提示和技巧提供了基于经验的见解,但各个系统配置和性能可能会有所不同。 始终建议对不同的设置进行试验和基准测试,以找到最适合你的特定需求的解决方案。

参考链接


鸿蒙应用开发:提升性能的利器——防抖与节流

为了应对频繁的事件调用所带来的性能瓶颈,我们需要引入防抖(debounce)和节流(throttle)这两个利器。

防抖:抑制不必要的高频事件

防抖是一种技术,它可以在一定时间内抑制不必要的高频事件。在鸿蒙应用开发中,防抖可以用来优化事件处理,例如按钮点击事件。

防抖的工作原理是:当一个事件被频繁调用时,防抖函数会将事件放入一个计时器中。在这个计时器中,事件将在一个指定的等待时间后执行。如果在等待时间内又收到了该事件的调用,那么计时器将被重新设置,等待时间将从头开始计算。

节流:限制事件调用的频率

节流是一种技术,它可以限制事件调用的频率。在鸿蒙应用开发中,节流可以用来优化输入框中的输入事件处理。

节流的工作原理是:当一个事件被频繁调用时,节流函数会将事件放入一个令牌桶中。在这个令牌桶中,事件将按照一个固定的速度执行。如果令牌桶已满,那么事件将被丢弃。

防抖与节流的比较

防抖和节流都是用来处理高频事件的利器,但是它们的工作方式不同。

  • 防抖: 在一定时间内只执行一次事件。
  • 节流: 以固定的速度执行事件,丢弃多余的事件。
在鸿蒙应用开发中的应用

防抖和节流可以在鸿蒙应用开发中得到以下应用:

  • 事件处理: 防抖可以用来优化按钮点击事件等高频事件的处理,避免不必要的高频调用。
  • 输入框输入: 节流可以用来限制输入框中输入事件的频率,避免不必要的多余请求。
  • 网络请求: 防抖和节流可以用来优化网络请求,避免不必要的多余请求。
  • 滚动事件: 节流可以用来限制滚动事件的频率,避免不必要的高频调用。

示例代码

总结

防抖和节流是鸿蒙应用开发中提升性能的利器。通过合理使用防抖和节流,可以有效优化事件处理、输入框输入、网络请求和滚动事件,从而提升应用的整体性能和用户体验。

参考链接


蓝牙BLE: GATT Profile 简介(GATT 与 GAP)

一. 引言

现在低功耗蓝牙(BLE)连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。

二. GAP

详细介绍 GATT 之前,需要了解 GAP(Generic Access Profile),它在用来控制设备连接和广播。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。例如 Beacon 设备就只是向外广播,不支持连接,小米手环就等设备就可以与中心设备连接。

2.1 设备角色

GAP 给设备定义了若干角色,其中主要的两个是:外围设备(Peripheral)和中心设备(Central)。

外围设备:这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备。例如小米手环。
中心设备:中心设备相对比较强大,用来连接其他外围设备。例如手机等。

2.2 广播数据

在 GAP 中外围设备通过两种方式向外广播数据: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备的名字。(广播的数据格式我将另外专门写一个篇博客来讲。)

2.3 广播流程

GAP 的广播工作流程如下图所示。

从图中我们可以清晰看出广播数据和扫描回复数据是怎么工作的。外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。

2.4 广播的网络拓扑结构

大部分情况下,外设通过广播自己来让中心设备发现自己,并建立GATT连接,从而进行更多的数据交换。也有些情况是不需要连接的,只要外设广播自己的数据即可。用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。因为基于GATT连接的方式的,只能是一个外设连接一个中心设备。使用广播这种方式最典型的应用就是苹果的iBeacon。广播工作模式下的网络拓扑图如下:

三. GATT

GATT 的全名是 Generic Attribute Profile(姑且翻译成:普通属性协议),它定义两个 BLE 设备通过叫做 Service 和 Characteristic 的东西进行通信。GATT 就是使用了 ATT(Attribute Protocol)协议,ATT 协议把 Service, Characteristic遗迹对应的数据保存在一个查找表中,次查找表使用 16 bit ID 作为每一项的索引。

一旦两个设备建立起了连接,GATT 就开始起作用了,这也意味着,你必需完成前面的 GAP 协议。这里需要说明的是,GATT 连接,必需先经过 GAP 协议。实际上,我们在 Android 开发中,可以直接使用设备的 MAC 地址,发起连接,可以不经过扫描的步骤。这并不意味不需要经过 GAP,实际上在芯片级别已经给你做好了,蓝牙芯片发起连接,总是先扫描设备,扫描到了才会发起连接。

GATT 连接需要特别注意的是:GATT 连接是独占的。也就是一个 BLE 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当设备断开,它又开始广播。

中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。

3.1 GATT连接的网络拓扑

下图展示了 GTT 连接网络拓扑结构。这里很清楚的显示,一个外设只能连接一个中心设备,而一个中心设备可以连接多个外设。

 一旦建立了连接,通信就是双向的了,对比前面的GAP广播的网络塔扑,GAP通信是单向的。如果你要让两个外围设备能通信,就只能通过中心设备中转

3.2 GATT 通信事务

GATT通信的双方是C/S关系。外设作为GATT服务端(Server),它维持了ATT的查找表以及service和characteristic的定义。中心设备是GATT客户端(Client),他向Server发起请求。需要注意的是,所有的通信事件,都是由客户端(也叫主设备,Master)发起,并且接收服务端(也叫从设备,Slava)的响应。
一旦连接建立,外设将会给中心设备建议一个 连接间隔(Connection Interval),这样,中心设备就会在每个连接间隔尝试去重新连接,检查是否有新的数据。但是,这个连接间隔只是一个建议,你的中心设备可能并不会严格按照这个间隔来执行,例如你的中心设备正在忙于连接其他的外设,或者中心设备资源太忙。

下图展示一个外设(GATT服务端)和中心设备(GATT客户端)之间的数据交流流程,可以看到的是,每次都是主设备发起请求:

3.3 GATT 结构

GATT事务是建立在嵌套的Profiles,Services和Characteristics之上的,如下如所示:

  • Profile Profile并不是实际存在于BLE外设上的,它只是一个被Bluetooth SIG或者外设设计者预先定义的Service的集合。例如心率Profile(Heart Rate Profile)就是结合了Heart Rate Service和Device Information Sercvice。所有官方通过GATT Profile的列表可以从这里找到。

  • Service Service是把数据分成一个个的独立逻辑项,它包含一个或者多个Characteristic。每个Service有一个UUID唯一标识。UUID有16bit的,或者128bit的。16bit的UUID是官方通过认证的,需要花钱购买,128bit是自定义的,这个就可以自己随便设置。

    官方通过了一些标准Service,完整列表在这里。以Heart Rate Service为例,可以看到它的官方通过16bitUUID是 0x180D,包含3个Characteristic:Heart Rate Measurement,Body Sensor Location和Heart Control Point,并且定义了只有一个第一个必须的,它是可选实现的。

  • Characteristic 在GATT事务中的最低界别的是Characteristic,Characteristic是最小的逻辑数据单元,当然它可能包含一个组关联的数据,例如加速度计的X/Y/Z三轴值。与Service类似,每个Characteristic用16bit或者128bit的UUID唯一标识。你可以免费使用Bluetooth SIG官方定义的标准Characteristic,使用官方定义的,可以确保BLE的软件和硬件能相互理解。当然,你可以自定义Characteristic,这样的话,就只有你自己的软件和外设能够相互理解。

    举个例子,Heart Rate Measurement Characteristic,这是上面提到的Heart Rate Service必需实现的Characteristic,它的UUID是 0x2A37。它的数据结构是,开始8bit定义心率数据格式(是UINT8还是UINT16?),接下来就是对应格式的实际心率数据。

    实际上,和BLE外设打交道,主要是通过Characteristic。你可以从Characteristic读取数据,也可以往Characteristic写数据。这样就实现了双向的通信。所以你可以自己实现一个类似串口(UART)的service,这个Service中包含两个Characteristic,一个被配置只读的通道(RX),另一个配置为只写的通道(TX)。

更多内容

参考链接


蓝牙BLE: GATT Profile 简介(GATT 与 GAP)

“真男人就应该用 C 编程”!用 1000 行 C 代码手搓了一个大模型,Mac 即可运行,特斯拉前AI总监爆火科普 LLM

徒手用 1000 行 C 语言实现,不依赖庞大的外部库,Mac 即可运行。   

如今这年头,徒手写神经网络代码已经不算事儿了,现在流行手搓大模型训练代码了!这不,今天,特斯拉前 AI 总监、OpenAI 创始团队成员 Andrej Karpathy 仅用 1000 行简洁的 C 代码,就完成了 GPT-2 大模型训练过程。

继续阅读“真男人就应该用 C 编程”!用 1000 行 C 代码手搓了一个大模型,Mac 即可运行,特斯拉前AI总监爆火科普 LLM

解决WordPress 6.x缺少ICP备案链接的问题

早期版本的 WordPress 内置了显示网站备案号的功能(WordPress 5.x 以及之前的版本,参考: 解决WordPress 5.2.3/5.7.2后台ICP备案链接不能跳转到工信部网站(www.miitbeian.gov.cn)的问题),但是升级到 WordPress 6.x 版本之后,这部分功能被丢弃了。 

但是这又是工信部的规定,因此我们需要手工修改代码解决这个问题。

一般都是手工修改当前使用主题的 footer.php,增加工信部相关的配置,如下:

完整的增加位置如下:

参考链接


安泰信-二合一维修系统-AT8502D-无法长时间工作

安泰信8502D,热风枪焊台一体的,最近出了个问题:

焊台工作大概20分钟后频烦出现自动停止加热,回到待机状态,接着再开机立马就停,有的时候甚至开不了机,按键无反应!

断电几个小时候后再开机,还能用20分钟或半个小时,然后继续重复以上的故障现象。

继续阅读安泰信-二合一维修系统-AT8502D-无法长时间工作

Armorly A1-70W型车载电动充气洗车一体机更换LET隔膜泵

七年前买车的时候买的电动充气洗车一体机,型号信息(Armorly A1-70W)。没有用几次,时间长了以后,水泵就就坏了,不出水。一直也没丢弃,这些天恰好翻出来,准备修理一下。

拆外壳是非常简单的,只需要拧下背部的八颗螺丝即可。拆水泵只需要拧下额外的四颗螺丝即可。

继续阅读Armorly A1-70W型车载电动充气洗车一体机更换LET隔膜泵

Foxmail 本地邮箱密码破解思路方法分享

本文主要以 POP3 为例讲解, 其他邮件协议可以参考思路, 自行尝试解决。

最近发生了一件比较尴尬的事, 公司邮箱密码忘记了, 又不想麻烦 IT 部门更改, 就想尝试下自己破解下本地的密码。 (密码已经以加密形式保存在本地电脑)

看到网上分享的一些办法, 如下:

  • 破解本地密码文件。(密文通过秘钥(不通版本秘钥有差别), 异或运算计算出的密文密码, 解密就是按照加密规则逆运算回去)
  • 去掉 SSL 访问, 用抓包工具( Wireshark 等)抓取明文数据。

第一种耗时耗力, 版本差异引起方法不通, 还需要破壳工具啥的自己去实际抓抓。

第二种不能用, 公司邮箱服务不允许明文连接, 加密数据不好破解。

所以我用了另一种方式, 下面直接分享步骤和代码, 后面再分享思路

  1. 更改 hosts 文件, 添加如下内容:
  2. 更改 Foxmail 邮箱服务配置, 去掉 SSL

  3. 启动 Python 写的服务程序, 代码如下:

  4. Foxmail 中点击 “收件” , Python 服务打印用户名密码:

    本地的加密用户名就获取到了。

下面说下思路。其实思路也很简单, 就是模拟 POP3 协议, 写个假的 POP3 服务, 然后让 Foxmail 连接这个POP3 服务, 并把用户名和密码发送给我们的 POP3 服务。也是参考抓包提取密码的方法。只是没见过其他人分享过, 自己就分享了下, 其他邮件协议也可以参考下, 不需要把邮件协议完全模拟出来, 只要能够骗过 Foxmail 把用户名密码传过来认证就可以了。

下面大体说下 POP3 协议:

  1. TCP 三次握手, 连接到 POP3 服务
  2. 服务端发送 “+OK...” 信息, 表示服务已经准备好, 等待客户端发送认证信息。( POP3 消息边界符也是 CRLF, 别忘记在消息后面添加)
  3. 客户端 发送 USER <邮箱名> 到 POP3服务
  4. POP3 返回 “+OK” 消息, 等待客户端发送密码认证
  5. 客户端发送 PASS <邮箱密码> 到POP3服务
  6. POP3 返回  “+OK” 消息, 表示认证成功, 就可以等待客户端接下来的操作。
  7. 客户端发送 QUIT 表示断开连接。

基于这个步骤, 我们就可以写个模拟 POP3 协议的服务, “骗取” Foxmail 的本地密码。

当然这种只适合用户忘记本地密码。

参考链接


Foxmail 本地邮箱密码破解思路方法分享

Javascript中this指向丢失原因及解决办法详解

大家都知道 JS 中的 this 关键字通常出现在函数或者方法中,用来指向调用该函数或者方法的对象。但是在很多时候 this 的指向却并不总是如我们所愿,这一篇文章就一起来看看到底该如何判断 this 所指向的对象,同时在 this 指向丢失情况下如何恢复。

this指向丢失

相信有过面向对象编程经验的朋友对于 this 的使用不会陌生,来看两个例子

这里的 this 指向的是构造函数生成的对象 zhangsan,对象调用自身的方法 sayHello(),其中的 this 自然不会有什么指向问题。

这里只是把构造函数换成了 class 语法的方式,this 指向的是类实例 xiaofu,实例调用自身的方法,其中的 this 也不会有什么指向问题。

但是再看下面这个例子

注意 setTimeout 的第一个参数是一个函数名,而并不是具体的函数调用。

所以这里并不能直接传递 zhangsan.sayHello(),不然会马上执行。

本意是想等待 2 秒之后再打印,结果打印完发现 this.name 并没有打印出来,this 指向丢失了。按照 this 指向调用函数的对象的逻辑,说明 2 秒后调用 sayHello() 这个方法的已经不是 zhangsan 这个对象了。

如果在方法中打印一下 this,就会发现此时 this 指向的是 Window。也就是说最后一句可以像如下改写

执行异步操作的时候是将一个函数丢给浏览器,2 秒以后,浏览器去直接执行该函数。

这时候可以引出一个重要的结论:包含 this 的函数无法在定义的时候,而只有在被真正执行的时候才能知道this指向哪个对象。

再看下面的例子就很容易理解了

因为 sayHello 这个方法真正执行的时候是被 lisi 这个对象调用,所以 this 指向的是 lisi 这个对象,this.name 打印了出来也是 lisi

多重调用以及箭头函数

可能有朋友又要问了,那我直接执行 zhangsan.sayHello() 不也是相当于在 Window 中去执行这个函数吗?

让我们再看下面这个例子

这里调用的 sayHello 函数是 info 对象下的,可以看到函数中的 this 指向的是 info 对象,而并不是 zhangsan 对象。这里又可以引出另外一个重要的结论:多重调用下,函数中的 this 只会指向函数的上一级对象。这里函数的上一级对象是 info,所以虽然 zhangsan 中也有一个 name,但是并不会被引用。

但是这里需要注意的是箭头函数。

箭头函数在 ES6 中被引入,写起来简洁明了,但是有一个特点需要注意,就是箭头函数没有独立的 this,其中的this 会自动从上一级继承。

所以如果改写下上面的代码

可以看出,箭头函数中使用 this 就和直接在 info 中使用 this 效果一样,都是指向 zhangsan 对象。

this指向丢失解决办法

再把话题回到 this 丢失上面来。

想要恢复 this 指向,根本逻辑就是想办法还是让 this 定义时候的对象来调用 this 所在的函数,回到上面的例子就是让 zhangsan 来调用 sayHello()

有两种方式可以来实现,第一种是多添加一层函数调用

这里的最后一句相当于 Window.zhangsan.sayHello(),根据上面的规则,this 指向的是上一级对象,也就是zhangsan,所以可以成功打印出来。

并且这里使用箭头函数同样有效果,因为这里的函数只是起到多加一层包装的作用,并没有实际作用。

这里要特别说明一下特殊情况:

这个情况不是很好理解,为什么箭头函数包装的 this 就可以传递过去?

其实,更方便理解的等价的写法如下:

 注意如下函数:

这样修改会更方便理解。其实本质上箭头函数本质上就是一个对象,这个对象保持了对外部对象的引用,因此不会出现 this 丢失的情况。

第二种方式是利用函数的 bind 方法,使用语法如下

这里就是将函数 func 绑定到了 context 这个上下文上,返回一个新的函数。不管被谁调用,这个新的函数里面的this 永远指向 context

这里就是将 sayHello() 这个方法绑定到了 zhangsan 这个对象上,以后不管这个返回的新函数被谁调用,都可以成功返回 zhangsan 中的 this.name

但是这里要注意的是,只能绑定到构造函数返回的具体对象上,而不能直接绑定到类名 Student 上。

同时要注意 bind 并不支持级联操作

这里首先将函数f绑定到一个对象,然后马上级联操作绑定到另一个对象,可以看出只有第一个 bind 起了效果。

同时这里也可以看到只有在函数执行的时候才会将 this 指向具体的对象

bind传递函数参数

这里再提一个 bind 方法的进阶用法,就是固定函数传递的一部分参数值,有一点类似 python 中的 partial 函数。因为 bind 方法除了第一个参数是上下文,后面还可以接函数的默认参数值

这里修改了 sayHello() 方法,必须要传递一个参数,如果想以后每次执行该方法的时候都是传递参数 99 就可以像上面那样。

下面来一个更通用的例子。

有一个需要传递两个参数的函数如下

通过 bind 方法将第一个参数值默认为 99,并返回一个新函数。这里因为没有 context 需要传递,所以第一个参数放 null,不能省略

注意这里只能是按照参数的先后顺序进行默认值传递,例如这里就不能跨过 agename 传递默认值。

总结

JS 中的 this 使用起来并不像其他 OOP 语言中的类似关键字方便(例如 python 中的 self),因为有指代丢失的问题出现,只能是在实际使用的时候多多练习,熟能生巧了。

参考链接


Javascript中this指向丢失原因及解决办法详解