GGML源码逐行调试(上)
目录
- 前言
- 1. 简述
- 2. 环境配置
- 3. ggml核心概念
- 3.1 gguf
- 3.2 ggml_tensor
- 3.3 ggml_backend_buffer
- 3.4 ggml_context
- 3.5 backend
- 3.6 ggml_cgraph
- 3.7 ggml_gallocr
- 4. 推理流程整体梳理
- 4.1 时间初始化与参数设置
- 4.2 模型加载与词汇表构建
- 4.3 计算图与内存分配
- 4.4 文本预处理与推理过程
- 4.5 性能统计与资源释放
- 4.6 完整推理流程
- 结语
- 下载链接
- 参考
前言
学习 UP 主 比飞鸟贵重的多_HKL 的 GGML源码逐行调试 视频,记录下个人学习笔记,仅供自己参考😄
refer1:【大模型部署】GGML源码逐行调试
refer2:llama.cpp源码解读–ggml框架学习
refer3:https://github.com/ggml-org/ggml
refer4:https://chatgpt.com/
1. 简述
本来想直接上手学习 llama.cpp 的,但是在读源码时发现很多概念都没接触过,所以这里还是花一些时间从简单的 ggml 框架开始学习
ggml 是一个用 c/c++ 编写、专注于 transformer 架构模型推理的机器学习库,它和 pytorch、tensorflow 等机器学习库比较类似。github 地址是:https://github.com/ggml-org/ggml
关于 ggml 更多的介绍大家可以参考 Georgi Gerganov 在 hugging face 上发布的一篇文章:Introduction to ggml
2. 环境配置
ggml 本身没有什么依赖,并且它的 README 文档对其环境配置已经描述得很清楚了,需要注意的是 ggml 提供了很多案例,例如 sgemm、mnist、yolo、sam、gpt-2 等等,这里我们主要分析的案例是 gpt-2 的推理
下面我们来简单过下如何利用 ggml 推理 gpt-2
1. 代码克隆,指令如下:
git clone https://github.com/ggml-org/ggml
Note:大家也可以点击 here 下载博主准备好的源码(注意该代码下载于 2025/4/12 日,若后续有改动请参考最新)
2. 模型下载,指令如下:
cd ggml
bash examples/gpt-2/download-ggml-model.sh 117M
等待一段时间后,在 models/gpt-2-117M 文件夹下会保存 ggml-model.bin 的模型文件
Note:模型下载需要访问外网,大家也可以点击 here 下载博主准备好的模型
3. 代码编译,指令如下:
cd ggml
mkdir build && cd build
cmake ..
cmake --build . --config Release -j 8
4. 执行推理,指令如下:
cd ggml
./build/bin/gpt-2-backend -m models/gpt-2-117M/ggml-model.bin -p "This is an example"
这里我们指定了模型的路径,并且给出了我们的 prompt 提示词 "This is an example"
输出如下图所示:
从上图中可以看到,我们成功执行了推理,此外终端输出的并不只有模型推理的结果,还包括很多其他的东西,例如模型加载时的一些信息打印、推理的耗时打印等等
值得注意的是推理时默认使用的后端是 CPU 后端,如果大家有 GPU 可以选择 CUDA 后端,我们调试时也是以 CUDA 后端来分析的
下面我们就来看看如何使用 CUDA 后端推理,指令如下:
cd ggml
mkdir build && cd build
cmake -DGGML_CUDA=ON -DCMAKE_CUDA_COMPILER=/usr/local/cuda-11.8/bin/nvcc ..
cmake --build . --config Release -j 8
cd ..
./build/bin/gpt-2-backend -m models/gpt-2-117M/ggml-model.bin -p "This is an example" -ngl 12
Note:其中 -DCMAKE_CUDA_COMPILER
参数指定的 nvcc 路径需要替换为你自己电脑上的路径
如果需要支持 CUDA 后端也很容易,只需要在编译时将 -DGGML_CUDA
开启,同时指定下 nvcc 的路径即可。另外在推理时需要新增一个 -ngl
的参数,它代表的含义是 number of layers to offload to GPU on supported models,也就是将模型的多少个 layer 放到 GPU 上去推理,默认 0
值得注意的是如果不指定 -ngl
参数,即便你在编译时开启了 CUDA 后端支持,在执行时也是不会在 GPU 上推理,因为模型加载函数中有如下的代码:
#ifdef GGML_USE_CUDAif (n_gpu_layers > 0) {fprintf(stderr, "%s: using CUDA backend\n", __func__);model.backend = ggml_backend_cuda_init(0);if (!model.backend) {fprintf(stderr, "%s: ggml_backend_cuda_init() failed\n", __func__);}}
#endif
我们可以从上述代码中发现,它通过宏定义 GGML_USE_CUDA
来判断是否使用 CUDA 后端来推理模型,此外只有当 n_gpu_layes
参数大于 0 时才会做 CUDA 后端的初始化,这里的 n_gpu_layes
变量就是我们上面提到的 -ngl
参数
CUDA 后端输出如下图所示:
CUDA 后端推理时在终端可以看到 GPU 设备的一些信息,另外可以看到每个 token 预测所使用的时间明显减少了
OK,以上就是利用 ggml 推理 gpt-2 时环境配置的整体流程了
既然要学习 ggml 的源码,肯定是需要 debug 逐行调试的,因此下面我们来配置下 debug 时的环境
Note:博主使用的是 vscode 编辑器,如果大家使用的是其它工具,可能需要自行配置
1. Debug 模式编译,指令如下:
cd ggml
mkdir build && cd build
cmake -DGGML_CUDA=ON -DCMAKE_CUDA_COMPILER=/usr/local/cuda-11.8/bin/nvcc -DCMAKE_BUILD_TYPE=Debug ..
cmake --build . -j 8
2. vscode 配置 c++ 的调试功能
主要是在 .vscode 文件夹下新建 tasks.json、launch.json 以及 c_cpp_properties.json 来进行调试,其中 c_cpp_properties.json 非必需,下面是它们的一些说明:
.vscode
:它是一个隐藏文件夹,其主要作用是以 .json 的方式保存相关配置信息,如编译任务的配置、调试任务的配置、工作环境的配置等等tasks.json
:用来配置编译文件的信息,用于生成可执行文件launch.json
:用来配置调试文件的信息,比如指定调试语言环境、指定调试类型等等c_cpp_properties.json
:配置 c/c++ 代码的智能提示,方便代码的书写
tasks.json 内容如下:
{// See https://go.microsoft.com/fwlink/?LinkId=733558// for the documentation about the tasks.json format"version": "2.0.0","tasks": [{"label": "build","type": "shell",// for cmake"command": "cd build && cmake --build . -j 8"}]
}
launch.json 内容如下:
{// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"name": "(gdb) 启动","type": "cppdbg","request": "launch","program": "${workspaceFolder}/build/bin/gpt-2-backend","args": ["-m", "models/gpt-2-117M/ggml-model.bin","-p", "This is an example","-ngl", "12"],"stopAtEntry": false,"cwd": "${workspaceFolder}","externalConsole": false,"MIMode": "gdb","setupCommands": [{"description": "为 gdb 启用整齐打印","text": "-enable-pretty-printing","ignoreFailures": true}],"preLaunchTask": "build"}]
}
c_cpp_properties.json 内容如下:
{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/include/**","${workspaceFolder}/examples/**","${workspaceFolder}/src/**","/usr/local/cuda-11.8/include/**"],"defines": ["__CUDACC__", "HAS_PYTHON"],"compilerPath": "/usr/bin/gcc", "cStandard": "gnu11","cppStandard": "gnu++14","intelliSenseMode": "linux-gcc-x64","configurationProvider": "ms-vscode.makefile-tools","browse": {"path": ["${workspaceFolder}/include/**","${workspaceFolder}/examples/**","${workspaceFolder}/src/**","/usr/local/cuda-11.8/include/**"],"limitSymbolsToIncludedHeaders": false,"databaseFilename": ""}}],"version": 4
}
Note:注意将 CUDA 头文件目录修改为你自己的路径
3. 打断点,进入调试
我们需要调试的文件是 main-backend.cpp,位于 examples/gpt-2 文件夹下,找到这个文件后在 main 函数中打一个断点,将上面的环境配置好后,按住 F5 即可进入调试,如下图所示:
接下来我们就可以愉快的调试代码了🤗
3. ggml核心概念
在分析调试代码之前,我们先来简单的梳理下 ggml 中的一些核心概念,博主本来是想放在代码中一边讲解一边梳理的,但是发现如果不事先了解下这些概念的话,在调试阅读源码时会比较困难
3.1 gguf
gguf 是一种文件格式,由 Georgi Gerganov 定义发布,用于存储使用 ggml 框架进行推理的模型文件。gguf 是一种二进制格式,旨在快速加载和保存模型。大部分模型是通过 pytorch 或者其他框架开发的,我们可以将它们转换成 gguf 格式并通过 ggml 框架推理
博主对于 ggml 框架中 LLM 的存储方式有些困惑,为什么不使用像 ONNX 这种能够直接查看计算图的存储格式,而是使用单独定义的 GGUF 格式呢?🤔
可能的原因有以下几点:(from ChatGPT)
1. 精细化的量化与推理优化
ggml 的设计初衷就是通过高效的量化和特殊的内存布局来实现本地 CPU/GPU 上的快速推理
- 常见的 ONNX 推理引擎也支持 FP16/INT8 等常用量化格式,但 ggml/llama.cpp 社区针对大模型场景做了许多的优化,例如 Q4_0、Q4_K、Q5_0、Q8_0 等更细粒度的专用量化方案
- ggml 在推理或加载时的某些操作不一定能直接在 ONNX 解析器中被高效地识别和执行。也就是说,如果使用 ONNX,仍然需要再去做针对性 “二次开发” 或适配,才能在内存带宽受限的环境中跑得足够快
2. 最小化对外部依赖和格式解析的需求
ONNX 文件的结构本身是基于 Protocol Buffers(protobuf),里面包含了大量描述算子的元数据、模型结构等,一个完整的 ONNX 模型包含算子定义(opset)、计算图、以及用于图推理的各种配置信息
- ggml/llama.cpp 这种使用场景往往是想把一切都打包到非常简洁的推理实现里,不依赖过多外部库,也不需要通用推理引擎去解析或执行复杂算子
- 只需要保留 “大语言模型的参数和最小必要的结构信息”,就可以完成推理,避免处理通用 ONNX 解析框架的开销和复杂度
3. 灵活的模型结构与定制的推理过程
大语言模型的推理过程是一个相对固定的 Transformer 堆叠(如解码器结构),而 ONNX 这种通用格式会把所有网络层、激活函数等一股脑儿封装在计算图中
- ggml/llama.cpp 以及衍生项目中,推理的具体步骤、batch 处理、KV Cache 等,往往都放在手写或者定制的 C/C++/Go/Python 逻辑里实现
- 由于模型结构和推理过程已经非常固定且可预测,所以常常不需要一个通用的 “模型结构描述 + 解析器” 来动态构建推理图。这时,存储权重时只要能快速映射到内存,按照适合的张量布局进行运算就足够了
4. 文件体积与加载速度
对大语言模型而言,模型文件本身动辄几十 GB。如果我们使用的是经过高度量化(如 Q4、Q5 等)的权重,加载时只需要非常小且固定的 “额外数据” 即可开始推理,减少了对复杂解析的依赖
- 在 ONNX 中,权重依旧要打包到同一个 protobuf 中,但如何高效地组织这些量化后的张量(并尽量减少不必要的结构信息)就相对繁琐
- GGUF 则是专为量化权重在本地高效加载而生,为此精心做了格式上的取舍、适配与优化
5. 更适合「社区量化版本」与开源生态
ggml/llama.cpp 以及对应的 GGUF 文件,能非常快速地衍生出社区版本:
- 比如将原始模型转换成多种量化精度版本,只需要在转换脚本中调用相关函数,就能快速把原始 FP32/FP16 权重转成 Q4、Q5、Q8 等多种量化精度
- 再加上 GGUF 文件通常很轻量,格式相对简单,社区可以无需 ONNXRuntime、TensorRT 等推理引擎,也能低门槛地进行本地推理
总体来说,ONNX 适合更通用、更跨平台的模型交换,尤其在多框架支持和通用推理场景下非常有用。但是像 ggml/llama.cpp 这类专注于 “在 CPU 或 GPU 上高效推理大语言模型” 并且大量使用细粒度量化策略的场景,就会选择与自身开发思路契合的专用格式(如 GGUF),省去对通用计算图进行解析和适配的麻烦,也能高度控制在加载、推理时的内存布局和性能表现
gguf 文件结构如下图所示:
gguf 文件中各字节代表的含义如上图所示,它们使用在 general.alignment
元数据字段中指定的全局对齐方式,在需要时,gguf 文件会以 0x00
字节填充到 general.alignment
的下一个倍数。除非另有说明,否则字段(包括数组)都是按顺序写入的,不会对齐
关于 gguf 更详细的说明,大家感兴趣的可以看看官方说明文档:https://github.com/ggml-org/ggml/blob/master/docs/gguf.md
3.2 ggml_tensor
ggml_tensor
是 ggml 框架中用于表示 n 维张量(tensor)的结构体,可以类比于 pytorch 中 tensor 的概念
其定义如下:
/* include/ggml.h */// n-dimensional tensor
struct ggml_tensor {
enum ggml_type type;struct ggml_backend_buffer * buffer;int64_t ne[GGML_MAX_DIMS]; // number of elements
size_t nb[GGML_MAX_DIMS]; // stride in bytes:// nb[0] = ggml_type_size(type)// nb[1] = nb[0] * (ne[0] / ggml_blck_size(type)) + padding// nb[i] = nb[i-1] * ne[i-1]// compute data
enum ggml_op op;// op params - allocated as int32_t for alignment
int32_t op_params[GGML_MAX_OP_PARAMS / sizeof(int32_t)];int32_t flags;struct ggml_tensor * src[GGML_MAX_SRC];// source tensor and offset for views
struct ggml_tensor * view_src;
size_t view_offs;void * data;char name[GGML_MAX_NAME];void * extra; // extra things e.g. for ggml-cuda.cuchar padding[8];
};
其中:(from ChatGPT)
type
字段表示张量的数据类型,它是一个枚举类(ggml_type
)。ggml 支持多种数据类型,例如GGML_TYPE_F32
、GGML_TYPE_F16
、GGML_TYPE_Q4_0
等等buffer
字段是一个指向ggml_backend_buffer
结构的指针,指向的是能够操作该张量对应的数据块的接口,而实际 tensor 对应的数据存储地址为data
指针指向的位置。该结构体我们后续会做详细分析ne
是一个整数数组,表示张量每一维的大小。例如,对于 3 维张量,ne
数组将包含三个数值分别表示每一维的大小nb
数组表示每一维度的字节步长(stride)。步长用于描述内存中不同维度元素的存储距离,例如,nb[0]
是类型元素的大小,nb[1]
是跨越第一维度所需的字节数,依此类推。它用于高效地访问多维数组中的元素,对于一个 4x3x2 的 tensor 来说,其步长nb
计算如下:
op
字段表示张量的操作类型,它也是一个枚举类(ggml_op
),将其设置为GGML_OP_NONE
时表示该 tensor 持有数据。而其它值表示不同的操作,例如GGML_OP_MUL_MAT
表示该 tensor 不保存数据,只表示其它两个 tensor 之间的矩阵乘法结果op_params
是一个整数数组,存储与张量操作相关的一些参数flags
字段存储张量的一些状态标志,可能用于标识张量的特性,如是否需要梯度计算等src
是一个指针数组,指向要在其间进行运算的 tensor。例如,如果op == GGML_OP_MUL_MAT
,那么src
将包含指向两个要相乘的 tensor 的指针,如果op == GGML_OP_NONE
,那么src
将是空的view_src
字段表示另一个张量的指针,若该 tensor 是其它 tensor 的引用时,引用的目标 tensor 指针为 view_srcview_offs
字段表示偏移量,即从view_src
中的哪个位置开始获取数据data
指向实际的 tensor 数据,如果该 tensor 是一个操作(例如op == GGML_OP_MUL_MAT
),则指向NULL
。它也可以指向另一个 tensor 的数据,即视图(view)name
是一个字符数组,用于存储张量的名词extra
是一个通用的指针,允许为张量存储额外的信息。在 CUDA 或其它后端中,它可能用于存储特定于设备的额外数据padding
是用来填充的空间,可能是为了确保结构体对齐,以便更高效地访问内存- 关于结构体对齐是怎么计算的,大家感兴趣的可以看看:结构体对齐怎么计算的,一个视频带你理解清楚
ggml_tensor
结构体定义了一个 n 维张量的各种属性,包括张量的维度信息、数据类型、内存存储、计算操作、源张量等。它主要涉及数据存储、计算图操作和后端支持(如 CUDA)的交互
在 理解llama.cpp如何进行LLM推理 文章中我们也有详细解释过 ggml 中的张量,大家感兴趣的可以看看
3.3 ggml_backend_buffer
在 ggml 框架中,一切数据(context、dataset、weight、output…)都应该被存放在 buffer 中,而之所以要用 buffer 进行集成承载不同的数据,主要是为了便于实现多种后端(CPU、GPU)设备内存的统一管理。也就是说 buffer 是实现不同类型数据在多种后端上进行统一的接口对象,其结构如下图所示:
在 ggml 框架中,buffer 结构体 ggml_backend_buffer
的设计涉及了较多的层次,使用了面向对象的编程模式来封装不同的功能。这种设计模式的核心目的是为每种设备(例如 CPU、GPU)提供一个通用的接口,同时又能根据设备的特性执行优化
其定义如下:
/* src/ggml-backend-impl.h */struct ggml_backend_buffer {struct ggml_backend_buffer_i iface;ggml_backend_buffer_type_t buft;void * context;size_t size;enum ggml_backend_buffer_usage usage;
};
我们可以按照以下几个关键组件来分析整个 buffer 的设计:
1. ggml_backend_buffer_i
ggml_backend_buffer_i
是一个结构体,其定义如下:
struct ggml_backend_buffer_i {// (optional) free the buffervoid (*free_buffer) (ggml_backend_buffer_t buffer);// base address of the buffervoid * (*get_base) (ggml_backend_buffer_t buffer);// (optional) initialize a tensor in the buffer (eg. add tensor extras)void (*init_tensor) (ggml_backend_buffer_t buffer, struct ggml_tensor * tensor);// tensor data accessvoid (*memset_tensor)(ggml_backend_buffer_t buffer, struct ggml_tensor * tensor, uint8_t value, size_t offset, size_t size);void (*set_tensor) (ggml_backend_buffer_t buffer, struct ggml_tensor * tensor, const void * data, size_t offset, size_t size);void (*get_tensor) (ggml_backend_buffer_t buffer, const struct ggml_tensor * tensor, void * data, size_t offset, size_t size);// (optional) tensor copy: dst is in the buffer, src may be in any buffer, including buffers from a different backend (return false if not supported)bool (*cpy_tensor) (ggml_backend_buffer_t buffer, const struct ggml_tensor * src, struct ggml_tensor * dst);// clear the entire buffervoid (*clear) (ggml_backend_buffer_t buffer, uint8_t value);// (optional) reset any internal state due to tensor initialization, such as tensor extrasvoid (*reset) (ggml_backend_buffer_t buffer);
};
它定义了一组指针函数(即接口),这些函数是针对缓冲区(buffer)操作的一些基本功能。_i
后缀通常表示 “interface”(接口),它是一种编程约定,用于区分结构体与它所提供的接口函数
这个结构体中的函数指针如下:
free_buffer
:释放缓冲区get_base
:获取缓冲区的首地址init_tensor
:在缓冲区中初始化张量memset_tensor
、set_tensor
、get_tensor
:用于张量数据的读写cpy_tensor
:在不同的缓冲区间进行张量数据的复制clear
:清空整个缓冲区reset
:重置缓冲区的内部状态
这些操作都是通过不同后端设备实现的具体细节来执行的
Note:结构体中的 ggml_backend_buffer_t
是 ggml_backend_buffer
结构体的指针类型,_t
后缀通常表示它是一个类型别名
2. ggml_backend_buffer_type_t
ggml_backend_buffer_type_t
是指向 ggml_backend_buffer_type
的指针类型,而 ggml_backend_buffer_type
结构体定义如下:
struct ggml_backend_buffer_type {struct ggml_backend_buffer_type_i iface;ggml_backend_dev_t device;void * context;
};
该结构体用于表示缓冲区的类型,每种缓冲区类型都会实现一组接口(通过结构体 ggml_backend_buffer_type_i
来定义),允许在不同的后端(如 CPU 和 GPU)上创建和管理缓冲区
Note:ggml_backend_dev_t
是指向 ggml_backend_device
的指针类型,它封装了后端设备的各种具体信息
ggml_backend_buffer_type_i
定义如下:
struct ggml_backend_buffer_type_i {const char * (*get_name) (ggml_backend_buffer_type_t buft);// allocate a buffer of this typeggml_backend_buffer_t (*alloc_buffer) (ggml_backend_buffer_type_t buft, size_t size);// tensor alignmentsize_t (*get_alignment) (ggml_backend_buffer_type_t buft);// (optional) max buffer size that can be allocated (defaults to SIZE_MAX)size_t (*get_max_size) (ggml_backend_buffer_type_t buft);// (optional) data size needed to allocate the tensor, including padding (defaults to ggml_nbytes)size_t (*get_alloc_size)(ggml_backend_buffer_type_t buft, const struct ggml_tensor * tensor);// (optional) check if tensor data is in host memory and uses standard ggml tensor layout (defaults to false)bool (*is_host) (ggml_backend_buffer_type_t buft);
};
其接口函数包括:
get_name
:获取缓冲区类型名alloc_buffer
:为指定类型分配缓冲区get_alignment
:获取缓冲区的对齐要求get_max_size
、get_alloc_size
:查询缓冲区的最大大小和分配大小is_host
:检查缓冲区是否位于主机内存 host 中
3. context
和 size
context
字段是一个指向 void
的指针,通常用于存储与特定后端或设备相关的额外信息,可能是设备上下文、内存管理上下文或者其他一些元数据
size
字段则表示缓冲区的大小
4. ggml_backend_buffer_usage
ggml_backend_buffer_usage
是一个枚举类型,用于描述缓冲区的用途,其定义如下:
enum ggml_backend_buffer_usage {GGML_BACKEND_BUFFER_USAGE_ANY = 0,GGML_BACKEND_BUFFER_USAGE_WEIGHTS = 1,GGML_BACKEND_BUFFER_USAGE_COMPUTE = 2,
};
其中:
GGML_BACKEND_BUFFER_USAGE_ANY
:通用缓冲区,可以用于任何用途GGML_BACKEND_BUFFER_USAGE_WEIGHTS
:用于存储权重数据的缓冲区GGML_BACKEND_BUFFER_USAGE_COMPUTE
:用于计算的缓冲区
buffer 的整个设计的核心思想是将不同的缓冲区操作抽象成接口,通过 ggml_backend_buffer
、ggml_backend_buffer_type
和 ggml_backend_device
等结构体实现对不同硬件(如 CPU 或 GPU)的支持。这样,通过统一的接口和抽象层,框架能够灵活地支持多种设备和后端,同时优化内存管理和数据传输
3.4 ggml_context
context 直译为 “上下文”,在 ggml 框架中会出现各种以 context 为名称的变量,在此框架中我们可以把它翻译为 “环境信息”,类似于操作系统的 “环境变量”(针对不同的程序需要设置不同的环境变量)
对于 ggml 框架来说,无论你要做什么(例如构建 model、建立计算图等)都需要先创建一个 context 作为容器,而你所创建的任何信息结构体(tensor、graph、…)实际都存储在 context 容器包含的地址空间内
下面我们以 ggml_context
为例来讲解,ggml_context
主要用于管理和存储与计算图或张量操作相关的上下文信息,可以承载三种数据类型:Tensor、Graph、Work_buffer。它可以被看作是一个 内存管理 和 资源组织 的中心,负责管理计算中使用的所有对象和内存,内部结构如下图所示:
Note:图片来自于:llama.cpp源码解读–ggml框架学习
其定义如下:
/* src/ggml.c */struct ggml_context {size_t mem_size;void * mem_buffer;bool mem_buffer_owned;bool no_alloc;int n_objects;struct ggml_object * objects_begin;struct ggml_object * objects_end;
};
其中:
mem_size
字段表示ggml_context
所管理的内存总大小(以字节为单位)mem_buffer
是一个指向内存缓冲区的指针,之前说过 ggml 中一切数据都要放在 buffer 中,这里的ggml_context
也不例外mem_buffer_owned
字段标识ggml_context
是否拥有mem_buffer
。- 如果为
true
,意味着ggml_context
自己负责管理和释放这个内存缓冲区 - 如果为
false
,表示内存由其他部分管理,ggml_context
仅用于访问
- 如果为
no_alloc
字段表示ggml_context
是否允许内存分配保存实际数据n_objects
字段表示ggml_context
管理的对象数量objects_begin
指向第一个ggml_object
的指针,标记了管理对象链表的起始位置objects_end
指向最后一个ggml_object
的指针,它表示当前上下文中对象链表的结束位置。通过这两个指针(objects_begin
和objects_end
),可以遍历所有的ggml_object
,从而管理所有的计算对象
ggml_object
结构体的定义如下:
enum ggml_object_type {GGML_OBJECT_TYPE_TENSOR,GGML_OBJECT_TYPE_GRAPH,GGML_OBJECT_TYPE_WORK_BUFFER
};struct ggml_object {size_t offs;size_t size;struct ggml_object * next;enum ggml_object_type type;char padding[4];
};
ggml_object
代表了 ggml 框架中的一个对象,在 ggml_context
中,每个对象都有以下字段:
offs
字段表示对象在mem_buffer
中的偏移量,也就是说,offs
是指向该对象数据在内存中的位置size
字段表示对象的大小,即该对象所占用的内存大小next
是一个指向下一个ggml_object
的指针。这种链表结构允许将多个ggml_object
组织成一个有序的集合,并且可以依次遍历它们type
字段表示对象的类型。它是一个枚举类,定义了几种不同的对象类型,主要有:GGML_OBJECT_TYPE_TENSOR
:表示一个张量对象GGML_OBJECT_TYPE_GRAPH
:表示一个计算图对象GGML_OBJECT_TYPE_WORK_BUFFER
:表示一个工作缓冲区对象
padding
数组用于填充,确保结构体的内存对齐
总的来说,ggml_context
类似于一个 “容器” 或 “管理器”,负责保存和组织计算图中的所有对象,它让你可以灵活地处理张量、计算图等对象的内存分配、释放和管理。
3.5 backend
ggml 的后端 backend 有如下几个比较重要的概念:(from here)
ggml_backend
:执行计算图的接口,有多种类型,包括:CPU(默认)、CUDA、Metal(Apple Silicon)、Vulkan 等等ggml_backend_buffer
:表示后端 backend 分配的内存空间ggml_backend_sched
:一个调度器,使得多种后端可以并发使用。在处理大模型或多 GPU 推理时,实现跨硬件平台地分配计算任务(如 CPU+GPU 混合计算)。该调度器还能自动将 GPU 不支持的算子转移到 CPU 上,来确保最优的资源利用和兼容性
具体的后端细节与后端调度器内容过多,博主能力有限这边就不分析了,大家感兴趣的可以自己看看相关源码
3.6 ggml_cgraph
计算图(computation graph,cgraph)是 Deep Learning 框架中常见的一个重要概念,用一句话通俗地说:计算图是一种有向图,用来描述计算的依赖关系和执行顺序
它的核心目的是:把复杂的数学表达式拆分成一系列的基本运算节点,然后用图的形式表示这些节点之间的依赖关系
举个例子,假设我们要计算 C = A × B + C C = A \times B + C C=A×B+C,根据 tensor 之间关系构建的计算图如下:
Note:图片来自于 llama.cpp源码解读–ggml框架学习
在上图中,分两种类型的元素:node 中间节点(白色)和 leaf 叶节点(粉红色)。一切既不是参数(weight)也不是操作数(即 ggml_tensor
结构体中的 op 为 None)的元素称之为 leaf,其余都作为 node(如 op 为 GGML_OP_MUL_MAT
)
图中 leaf_0
、leaf_1
和 leaf_2
都是 leaf 类型张量,是输入或常量张量,在计算中不会被修改,其中:
leaf_0 (f32) / CONST 0 [4, 3]
leaf_0
是view
的源张量CONST 0
表示它是个常量张量,数据类型为 float32,shape 为 4x3
leaf_1
等于 A A A,shape 为 2x4,leaf_2
等于 B B B,shape 为 2x3- 注意,ggml 内部会做隐式转换,将
leaf_1
的 shape 做转置变为 4x3
图中 node_0
是 node 类型张量,对应 ggml_tensor
中 op == GGML_OP_MUL_MAT
操作,也就是:
node_0 = ggml_mul_mat(leaf1, leaf2)
最后一个节点 node 有 (view) 标识,结合之前我们对 ggml_tensor
的讲解可知,这个节点是一个对原节点(leaf_0
)的引用,即将 A × B + C A \times B +C A×B+C 的计算结果直接存储在 C C C 矩阵的 tensor 中(leaf_0
),而不是重新建立一个新的 tensor 来进行存储
ggml 中的计算图结构体 ggml_cgraph
定义如下:
struct ggml_cgraph {int size; // maximum number of nodes/leafs/grads/grad_accsint n_nodes; // number of nodes currently in useint n_leafs; // number of leafs currently in usestruct ggml_tensor ** nodes; // tensors with data that can change if the graph is evaluatedstruct ggml_tensor ** grads; // the outputs of these tensors are the gradients of the nodesstruct ggml_tensor ** grad_accs; // accumulators for node gradientsstruct ggml_tensor ** leafs; // tensors with constant datastruct ggml_hash_set visited_hash_set;enum ggml_cgraph_eval_order order;
};
其中:
size
表示计算图中可容纳的最大节点数,包含中间节点、叶节点、梯度和梯度累加器n_nodes
表示当前图中用于计算的 中间节点 数量,比如加法、乘法这些操作形成的节点n_leafs
表示当前图中所有 叶节点 数量nodes
用来存放图中所有中间节点grads
用来存放图中每个计算节点对应的梯度张量grad_accs
用来存放梯度的累加器leafs
用来存放图中的叶子张量visited_hash_set
结构体利用哈希集合来记录哪些节点已经被访问过,用来避免重复处理节点(类似于图遍历中的 “标记是否访问过”)order
字段指定了计算图中节点的评估顺序
总的来说,ggml_cgraph
就是 ggml 中用来组织张量运算顺序、执行依赖、自动微分的计算图引擎核心结构
3.7 ggml_gallocr
我们需要知道,图 graph 是根据 ctx 中 tensor 之间的关系进行建立的,而 graph 中可能包含着输入数据、权重、新建的 node 节点,这些在 graph 中都只是数据的描述信息,而不是数据本身。因此我们还需要有一个专门的内存分配器来对我们计算图 graph 中所需要使用的全部数据进行内存分配,这就是 ggml_gallocr
所要做的事情
ggml_gallocr
定义如下:
/* src/ggml-alloc.c */struct ggml_gallocr {ggml_backend_buffer_type_t * bufts; // [n_buffers]ggml_backend_buffer_t * buffers; // [n_buffers]struct ggml_dyn_tallocr ** buf_tallocs; // [n_buffers]int n_buffers;struct ggml_hash_set hash_set;struct hash_node * hash_values; // [hash_set.size]struct node_alloc * node_allocs; // [n_nodes]int n_nodes;struct leaf_alloc * leaf_allocs; // [n_leafs]int n_leafs;
};
整个结构体分为以下几个部分:(from ChatGPT)
1. 缓冲区管理
bufts
与buffers
bufts
:存储了多个后端缓冲区类型(ggml_backend_buffer_type_t
),用于描述每个缓冲区的类型信息buffers
:存储实际的后端缓冲区(ggml_backend_buffer_t
),每个缓冲区代表一块内存区域,供计算图中张量数据存放n_buffers
:表示当前管理的缓冲区数量
buf_tallocs
- 每个缓冲区都有对应的动态张量分配器
ggml_dyn_tallocr
,用于管理该缓冲区内的内存分配 buf_tallocs
是一个指针数组,每个元素对应一个缓冲区内的动态分配器,它负责追踪该缓冲区的空闲内存块、对齐要求以及最大可分配内存量
- 每个缓冲区都有对应的动态张量分配器
2. 哈希集合管理
hash_set
与hash_values
- 在内存分配过程中,可能会遇到重复的分配请求或需要对已有分配做快速查找
hash_set
用来记录已经处理过的张量的信息,防止重复分配hash_values
是一个数组,存储每个键对应的hash_node
,其中每个hash_node
包含了分配状态、所在缓冲区、偏移量以及引用计数信息
3. 节点和叶子分配
node_allocs
与n_nodes
- 用于记录图中 计算节点(即中间计算结果张量)的内存分配情况
- 每个
node_alloc
结构体记录了一个节点的输出(dst)内存分配信息以及输入(src)的分配信息
leaf_allocs
与n_leafs
- 用于记录图中 叶子节点(通常为输入、常量或权重张量)的内存分配情况
- 每个
leaf_alloc
仅包装了一个tensor_alloc
结构体,记录了该叶子张量所在的缓冲区、内存偏移和分配的大小
整个 ggml_gallocr
(图内存分配器)的设计目的是为了高效管理计算图中各类张量的内存分配,它通过以下几个层面实现这一目标:
- 缓冲区与动态分配器管理
- 使用
buffers
、bufts
以及buf_tallocs
数组管理底层内存缓冲区,确保内存对齐和空闲块的合理利用
- 使用
- 哈希集合辅助配合
- 通过
hash_set
和hash_values
快速查找和管理重复分配、共享内存块,防止重复分配及内存浪费
- 通过
- 节点与叶子分配
- 分别使用
node_allocs
和leaf_allocs
记录计算节点和输入/常量张量的内存分配信息,保证计算图在执行前向传播和反向传播时内存访问正确、布局合理
- 分别使用
4. 推理流程整体梳理
在简单了解了 ggml 中的一些核心概念之后,我们先来把 ggml 推理 gpt-2 的总体流程过一遍,也就是先来把 main-backend.cpp 中的 main 函数梳理下,不跳进具体的函数,先把握下推理的整体过程
4.1 时间初始化与参数设置
在 main 函数中首先我们做了时间初始化与相关参数的一些设置
对于时间初始化,一开始调用了 ggml_time_init()
函数,并记录了程序开始时间,主要是方便后续统计模型加载、模型预测等各阶段的耗时
接着创建了一个 gpt_params
结构体,其定义如下,并将传入的命令参数进行解析并覆盖默认参数,在这里主要传入的是模型的路径、提示词文本等
struct gpt_params {int32_t seed = -1; // RNG seedint32_t n_threads = std::min(4, (int32_t) std::thread::hardware_concurrency());int32_t n_predict = 200; // new tokens to predictint32_t n_parallel = 1; // number of parallel streamsint32_t n_batch = 32; // batch size for prompt processingint32_t n_ctx = 2048; // context size (this is the KV cache max size)int32_t n_gpu_layers = 0; // number of layers to offlload to the GPUbool ignore_eos = false; // ignore EOS token when generating text// sampling parametersint32_t top_k = 40;float top_p = 0.9f;float temp = 0.9f;int32_t repeat_last_n = 64;float repeat_penalty = 1.00f;std::string model = "models/gpt-2-117M/ggml-model.bin"; // model pathstd::string prompt = "";std::string token_test = "";bool interactive = false;int32_t interactive_port = -1;
};
gpt_params
结构体主要用于存储 GPT 模型推理相关的参数,包括批处理大小(n_batch
)、上下文窗口大小(n_ctx
)、采样参数、提示词文本(prompt
)等等
4.2 模型加载与词汇表构建
参数设置完之后,我们开始利用这些参数来加载我们的模型,并构建词汇表
首先我们定义了 gpt_vocab
和 gpt2_model
两个结构体对象,分别用于存储词汇表和 GPT-2 模型相关的数据,它们的定义如下:
gpt_vocab
结构体定义:
struct gpt_vocab {using id = int32_t;using token = std::string;std::map<token, id> token_to_id;std::map<id, token> id_to_token;std::vector<std::string> special_tokens;void add_special_token(const std::string & token);
};
gpt_vocab
结构体中 token_to_id
成员变量用于从 token 到 ID 的映射,允许根据 token 查找对应的 ID,例如给定一个 token 如 "hello"
查找它在词汇表中的 ID,而 id_to_token
成员变量则刚好相反,用于从 ID 到 token 的反向映射,special_tokens
则存储着模型中一些特殊的 token 如 eos 等
gpt2_model
结构体定义:
struct gpt2_model {gpt2_hparams hparams;// normalizationstruct ggml_tensor * ln_f_g;struct ggml_tensor * ln_f_b;struct ggml_tensor * wte; // position embeddingstruct ggml_tensor * wpe; // token embeddingstruct ggml_tensor * lm_head; // language model headstd::vector<gpt2_layer> layers;// key + value memorystruct ggml_tensor * memory_k;struct ggml_tensor * memory_v;//struct ggml_context * ctx_w;struct ggml_context * ctx_kv;ggml_backend_t backend = NULL;ggml_backend_buffer_t buffer_w;ggml_backend_buffer_t buffer_kv;std::map<std::string, struct ggml_tensor *> tensors;
};
gpt2_model
结构体是 GPT-2 模型的核心部分,包含模型的各种参数:
hparams
是一个结构体,存储 GPT-2 的超参数,如隐藏层大小、词汇表大小等ln_f_g
和ln_f_b
是 LayerNorm 层的参数,分别表示缩放因子 scale 和偏置项 biaswte
和wpe
是两个 embedding 向量,分别表示 token 的 embedding 和 position 的 embedding,它们都是 ggml_tensor 结构体lm_head
是语言模型的头,用于计算最终的 logitslayers
存储了模型的多个层(通常是 Transformer 层)memory_k
和memory_v
是我们常说的 kv cachectx_w
和ctx_kv
是模型的上下文指针,其中ctx_w
可能与权重相关,ctx_kv
可能与 kv cache 相关backend
表示模型计算的后端,例如 CPU 或 GPUbuffer_w
和buffer_kv
用于存储模型计算过程中需要的缓存区tensors
用于存储模型中的各种张量,其中张量名字为键,张量对象为值
接着调用 gpt2_model_load
加载模型文件,同时将定义的 model
和 vocab
传入,这个函数是我们后续需要重点分析的,该函数包含模型权重加载、ggml 上下文的创建、后端初始化等等工作
4.3 计算图与内存分配
首先通过 ggml_gallocr_new
创建了一个图形分配器,该分配器基于模型后端默认的 buffer 类型
接着根据当前模型的上下文大小和批处理大小,构建了一个 “最坏情况” 下的计算图(cgraph
),它通过 gpt2_graph
函数来构建。然后调用 ggml_gallocr_reserve
根据计算图预留内存,并在终端打印所需的计算缓冲区大小
这个步骤涉及到 ggml 框架的核心数据结构之一:ggml_cgraph
(计算图),它用于组织计算步骤和内存管理。还有 ggml_gallocr
作为内存分配器,帮助估算和分配整个推理过程中需要的内存。计算图的构建和内存的分配也是我们后面需要重点分析的
Note:ggml 为了在运行阶段避免动态内存的申请,在计算开始之前会预分配一个 “足够大” 的内存空间。这种做法虽然可以提高运行效率,但也会带来一定的内存浪费,特别是在处理 kv cache 时。相比之下,其他一些推理框架如 vLLM,通过采用 PagedAttention 作为高速缓存管理层,在推理过程中能够更灵活地分配和释放内存。大家如果对 PagedAttention 感兴趣的话,可以看看 vLLM 的 Blog:vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention
4.4 文本预处理与推理过程
模型加载、计算图构建、context 创建、内存分配这些都做完之后就可以正式进入我们的推理环节了
推理第一步要做的就是 tokenization 分词,对输入的 prompt 使用 gpt_tokenize
函数进行分词,将输入的文本序列转化为对应 token 的 ID 序列。在我们的例子中,输入的 prompt 是 "This is an example"
,经过分词后得到了四个 token("This"
、"is"
、"an"
、"example"
) 的 ID
接着调整预测步数,确保整个输入加上预测的 token 数不超过模型上下文的最大长度
整个推理过程是以 token 为单位逐渐进行的(token-by-token),因此这里我们进入了一个 for 循环。当有 token(embd
不为空)时,调用 gpt2_eval
函数进行前向推理,更新 logits(概率分布)。
我们知道 gpt-2 的 vocabulary 词汇表的大小是 50257,因此这里我们得到了也刚好是 50257 个 logit,它们存储在一个 vector 容器中,每个 logit 是 float 类型的变量
推理完之后,更新累积已处理的 token 数(n_past
),并清空当前的 token 缓冲(embd
)
根据当前状态,若还处于处理初始 prompt,则按批处理一次性添加后续 token(else 分支,这里未执行)
若已经超过 prompt,则调用 gpt_sample_top_k_top_p
根据 logist 采样(温度采样),得到下一个 token,并将其加入上下文中(embd
),在我们的例子中,预测得到的第一个 token 的 id 是 286
每个生成的 token 都会被立即打印出来,并检查是否遇到结束符以终止生成,在我们的例子中生成的第一个 token 是 "of"
接着模型会根据 "of"
这个 token 预测下一个 token,如此反复循环,直至达到结束生成条件
这个过程是绝大部分 LLM 推理的通用过程,大家如果对 LLM 的推理流程不熟悉,可以看看 理解llama.cpp如何进行LLM推理 这篇文章,这里博主把 LLM 推理过程的流程图贴在下面了:
4.5 性能统计与资源释放
整个推理完成之后就是一些资源的释放工作了
在所有生成过程完成后,统计并打印模型加载时间、采样时间、预测时间(以及平均每 token 的预测时间)和总执行时间
最后,依次释放模型使用的资源,包括:
- 释放模型上下文(
ggml_free
) - 释放计算图内存分配器(
ggml_gallocr_free
) - 释放后端分配的缓冲区和后端资源(
ggml_backend_buffer_free
和ggml_backend_free
)
4.6 完整推理流程
OK,以上就是 ggml 推理 gpt-2 的整理流程梳理了
博主绘制了一个简单的流程图来描述这整个过程:
下面我们就来具体分析其中的每个步骤
Note:由于逐行代码调试展示篇幅占用太大,因此博主下面就只展示一些重点核心部分的函数,例如时间记录、命令行参数解析这类的函数就跳过了,大家如果想要了解更多的细节可以跟随 UP 主的视频一步步调试。另外想要说的是博主这边选取的案例是 gpt-2,UP 主选择的案例是 mnist,其实都大差不差,重点是学习 ggml 中的一些核心概念如 ctx、ggml_tensor、gguf、cgraph 等等,以便更好的学习 llama.cpp
由于篇幅原因,我们将核心部分的函数分析放在下篇文章来讲解😄
结语
这篇文章我们主要学习了利用 ggml 来推理模型(以 gpt-2 为例),同时梳理了 ggml 中的一些核心概念,包括 ggml_tensor、ggml_backend_buffer、ggml_context、ggml_cgraph 等等,大部分的概念解释都来自于 ChatGPT,这里博主对这些概念只是有个简单的印象,具体的设计以及实现博主其实也并不了解,大家可以自己多看看源码实现
OK,以上就是 ggml 推理 gpt-2 总体流程梳理的全部内容了,下篇文章我们就来进入到具体的函数中分析每个步骤都是如何实现的,敬请期待🤗
下载链接
- ggml源码下载链接【提取码:1234】
- gpt-2-117M模型下载【提取码:1234】
参考
- 【大模型部署】GGML源码逐行调试
- llama.cpp源码解读–ggml框架学习
- https://github.com/ggml-org/ggml
- https://chatgpt.com/
- 理解llama.cpp如何进行LLM推理
相关文章:
GGML源码逐行调试(上)
目录 前言1. 简述2. 环境配置3. ggml核心概念3.1 gguf3.2 ggml_tensor3.3 ggml_backend_buffer3.4 ggml_context3.5 backend3.6 ggml_cgraph3.7 ggml_gallocr 4. 推理流程整体梳理4.1 时间初始化与参数设置4.2 模型加载与词汇表构建4.3 计算图与内存分配4.4 文本预处理与推理过…...
智能测试用例生成:老旧平台页面查询功能的大模型改造
引言 由于GUI小工具【Deepseek APIPython 测试用例一键生成与导出】的不断升级实践,发现大模型的需求文档解析生成测试用例的可直接复用率不太理想,因此萌生了对老旧系统升级改造的想法。旧测试用例生成平台主要在于采集用户输入的字段名称、字段类型及…...
使用Python解决Logistic方程
引言 在数学和计算机科学中,Logistic 方程是描述人口增长、传播过程等现象的一种常见模型。它通常用于表示一种有限资源下的增长过程,比如动物种群、疾病传播等。本文将带领大家通过 Python 实现 Logistic 方程的求解,帮助你更好地理解这一经典数学模型。 1.什么是 Logist…...
文件上传基本原理靶场实现
一. 漏洞原理 未经验证的上传机制: 应用程序未对用户上传的文件进行充分验证,包括: 文件类型/扩展名:仅依赖客户端提交的MIME类型或简单检查扩展名(如.jpg),但未验证文件实际内容。 文件内容&a…...
灰色预测模型:GM(1,1)预测模型
灰色预测模型 灰色预测的主要特点是模型使用的不是原始数据序列,而是生成的数据序列。核心体系是灰色模型,即对原始数据作累加生成(或其他方法生成)得到近似的指数规律再进行建模的方法,优点是不需要很多的数据&#…...
SPORT(Serial Port)接口
SPORT(Serial Port)接口是DSP芯片中常用的高速同步串行通信接口,主要用于与外部设备(如ADC、DAC、编解码器等)进行数据传输。以下是对SPORT接口的详细介绍: 1. SPORT接口概述 SPORT接口设计用于高效传输连…...
Python及C++中的字典
一、Python中的字典 (一)基本概念 字典(dict)是Python中一种可变容器模型,用于存储键值对(key:value)。字典的键必须是不可变类型(如字符串、数字或元组),而…...
Neo4j GDS-11-neo4j GDS 库中相似度算法实现
neo4j GDS 系列 Neo4j APOC-01-图数据库 apoc 插件介绍 Neo4j GDS-01-graph-data-science 图数据科学插件库概览 Neo4j GDS-02-graph-data-science 插件库安装实战笔记 Neo4j GDS-03-graph-data-science 简单聊一聊图数据科学插件库 Neo4j GDS-04-图的中心性分析介绍 Neo…...
网络互连与互联网2
1.IP数据报首部在IHL 字段的最小值为5 2.三层交换机包括二层交换和三层转发,二层交换由硬件实现,三层转发采用软件实现 3.在BGP路由选择协议中,AS_PATH属性可以避免在AS之间产生环路 4.常用的电子邮件协议:SMTP(25&…...
4月12日随笔
今天大风天气的第一天,周六,早上九点半起来听了排球技术台培训。结果一天都没顾得上看教学视频。黄老师说有排球基础的可以试试当主裁,那一定要争取一下! 上午看了两集小排球,然后开始了解一些中介相关信息。因为下午…...
web自动化测试
自动化测试理论 UI:User Interface(用户接口-用户界面),主要包括:app、web ui自动化测试:使用工具或代码执行用例的过程 什么样的项目适合做自动化 1、需要回归测试项目(甲方自营项目、金…...
代码随想录二叉树小结1;(递归与迭代法小结)
一、递归遍历 1.递归算法三要素: 确定递归函数的参数和返回值: 在递归函数里加上递归的过程中需要处理的参数, 然后明确每次递归的返回值是什么,最后确定递归函数的返回类型。 确定终止条件: 递归算法运行的时候&…...
Audacity命令:“播录”菜单相关命令
1 Audacity命令:“播录”菜单相关命令 1.1 播录菜单 播录菜单中的命令可让您播放、停止、循环播放、擦洗音频或录制音频(包括定时录制和声控录制)。 Scripting IdActionParameters描述RescanDevice:Rescan Audio Devicesnone重新扫描连接到…...
Python及C++中的集合
1. Python 中的集合(set) 1.1 特性 无序性:集合中的元素没有顺序,不能通过索引访问。唯一性:集合中的元素不能重复,如果尝试添加重复的元素,集合会自动忽略。可变性:集合是可变的&…...
[CF2086E] Zebra-like Numbers 题解
确简单的啊,可是自己就是想不到。 考虑计算一个数的斑马值。贪心地,尽量选大的斑马数减即可。 考虑 DP,设 d p i , j dp_{i, j} dpi,j 表示 [ 1 , i ] [1, i] [1,i] 中斑马值为 j j j 的数的个数。那么显然有 d p i , j d p i − m …...
Linux-内核驱动
open uboot.bin target-connect U-Boot(Universal Boot Loader)是一种广泛使用的开源引导加载程序,它允许用户从各种设备(如硬盘、USB设备、网络等)加载操作系统。U-Boot提供了丰富的命令行接口(CLI&#…...
[Dify] 使用 Docker 本地部署 Dify 并集成 Ollama 模型的详细指南
在 AI 应用快速发展的今天,开源项目如 Dify 正成为构建本地化 AI 应用的利器。通过 Dify,你可以轻松地集成不同的大语言模型(LLM),如 Ollama,并快速创建可交互的 AI 应用。本篇文章将带你一步步通过 Docker…...
ESP32+Arduino入门(三):连接WIFI获取当前时间
ESP32内置了WIFI模块连接WIFI非常简单方便。 代码如下: #include <WiFi.h>const char* ssid "WIFI名称"; const char* password "WIFI密码";void setup() {Serial.begin(115200);WiFi.begin(ssid,password);while(WiFi.status() ! WL…...
软件架构评估两大法:ATAM 和 SAAM 的对比与实践
架构权衡分析方法(ATAM)和软件架构分析方法(SAAM)是软件架构评估领域中非常重要的两种方法,以下为你详细介绍: 一、架构权衡分析方法(ATAM) 1.背景与起源:ATAM 是由卡耐…...
《AI大模型应知应会100篇》第13篇:大模型评测标准:如何判断一个模型的优劣
第13篇:大模型评测标准:如何判断一个模型的优劣 摘要 近年来,大语言模型(LLMs)在自然语言处理、代码生成、多模态任务等领域取得了显著进展。然而,随着模型数量和规模的增长,如何科学评估这些模…...
编译 OpenCV 时,cmake 找到 TBB 的方法
编译 OpenCV 时,cmake 找到 TBB 的方法 编译 OpenCV 时,cmake 找到 TBB 的方法 编译 OpenCV 时,cmake 找到 TBB 的方法 HKEY_LOCAL_MACHINE\SOFTWARE\Intel\oneAPI\TBB\2022.1.0cmake 是从上面的注册表里找到的 TBB 安装路径。 这个键下面有…...
SMT贴片组装工艺优化与高效生产
内容概要 现代SMT贴片组装工艺的优化与高效生产涉及多维度技术协同,其核心在于构建精密可控的制造体系。本文系统梳理了从焊接参数调控到智能检测部署的全链路关键环节,重点解析影响生产效能的核心变量及其相互作用机制。通过对比不同贴装设备的速度-精…...
leetcode刷题日记——有效的数独
[ 题目描述 ]: [ 思路 ]: 题目要求求一个数独题目是否有效,即每行,每列,每个九宫格都不能出现相同的数字暴力求解:对每行,每列,以及九宫格进行检查是否存在相同数字运行如下 boo…...
Agent2Agent协议学习资源大全:从理论到实践
下面整理了一份关于谷歌 A2A 协议的学习资源,分为官方资源、开发教程/实践、行业解读和视频教程四个部分,方便您系统性地学习和掌握相关知识: 1. 官方资源 A2A 协议 GitHub 仓库 官方开源代码库,包含协议规范、示例代码和详细文档…...
PCDN收益高低的关键因素
PCDN(P2P内容分发网络)收益好的三个主要关键因素是:硬件配置与性能、网络环境与质量、业务调度与策略。 1. 硬件配置与性能 设备稳定性与兼容性 PCDN节点需长时间稳定运行,硬件性能直接影响收益。例如,使用高性能CPU、…...
ckeditor4.22版本 ckfinder php8版本下,上传提示400的问题
ckeditor4.22版本,ckfinder3,CKFinder 3 for PHP版本。 图片上传时提示:400错误,bad request。 通过编辑文件 D:\wamp\www\module\ckfinder\config.php 修改其配置: ini_set(display_errors, 1); $config[debug] tru…...
2025第四届大学生算法挑战赛 赛前测试赛 题解
前言 题解 测试考场【算法编程赛道】2025第四届大学生算法挑战赛 继续用Deepseek进行求解,还是非常丝滑。 A. 追债之旅 思路: 图论 bfs题 利用deepseek,直接给出答案 感觉deepseek在输入输出上,显得有些啰嗦。 # codingutf-8 import he…...
【软考系统架构设计师】系统配置与性能评价
1、 常见的性能指标 主频外频*倍频 主频1/CPU时钟周期 CPI(Clock Per Instruction)平均每条指令的平均时间周期数 IPC(Instruction Per Clock)每时钟周期运行指令数 MIPS百万条指令每秒 MFLOPS百万个浮点操作每秒 字长影响运算的…...
【软考系统架构设计师】信息系统基础知识
1、 信息的特点:客观性(真伪性)、动态性、层次性、传递性、滞后性、扩压性、分享性 2、 信息化:是指从工业社会到信息社会的演进与变革 3、 信息系统是由计算机硬件、网络和通信设备、计算机软件、信息资源、信息用户和规章制度…...
软考高级-系统架构设计师 其他知识补充
文章目录 系统配置与性能评价性能指标性能评价方法性能评测的常用方法 阿姆达尔解决方法 信息系统基础知识信息系统概述信息系统的分类(低级到高级)信息系统的生命周期(产生、开发、运行、消亡)信息系统开发方法 信息综合技术信息…...
基于查表法的 CRC8 / CRC16 / CRC32校验解析
在嵌入式开发中,CRC(Cyclic Redundancy Check)循环冗余校验算法广泛应用于通信数据校验、Flash 数据完整性检测、Bootloader 升级验证等场景。本文将深入剖析一套完整的 CRC8、CRC16 和 CRC32 实现,并通过查表法(Table…...
Web渗透之XSS注入
XSS的类型 1、反射型XSS 我们构建好一个urlXSS的payload,发送给受害者,受害者点击恶意链接后会在受害者的浏览器上执行恶意代码。反射型XSS是一次性的,而且比较容易被发现。通常恶意链接会被修改成短链接,或钓鱼图片的形式。 2…...
Eigen库的core模块源码阅读笔记
一、Eigen Core 模块概述 Eigen 是一个开源的C模板库,专注于线性代数运算(矩阵、向量、数值求解等)。其 Core 模块 是库的核心,定义了所有基础数据结构(如矩阵、向量、数组)和运算符重载,并实现…...
Blender安装基础使用教程
本博客记录安装Blender和基础使用,可以按如下操作来绘制标靶场景、道路标识牌等。 目录 1.安装Blender 2.创建面板资源 步骤 1: 设置 Blender 场景 步骤 2: 创建一个平面 步骤 3: 将 PDF 转换为图像 步骤 4-方法1: 添加材质并贴图 步骤4-方法2:创…...
网络相关基本概念
IP地址 描述一台主机在互联网上所处的位置。用32位整数表示。 用点分十进制表示。xxx.xxx.xxx.xxx表示。 端口号 区分当前主机上的指定的应用程序(进程)。一台主机上使用网络的程序有很多,可以通过端口号区分主机收到的数据要交给哪个程序…...
Prompt-Tuning方法
Fine-Tuning Fine-Tuning属于一种迁移学习方式,在自然语言处理(NLP)中,Fine-Tuning是用于将预训练的语言模型适应于特定任务或领域。Fine-Tuning的基本思想是采用已经在大量文本上进行训练的预训练语言模型,然后在小规模的任务特定文本上继续…...
C++ STL及Python中等效实现
1. STL 概述 STL 包含以下核心组件: 容器(Containers):存储数据的结构,如数组、链表、集合等。迭代器(Iterators):用于遍历容器的接口,类似指针。算法(Algo…...
动态规划(6)——01背包问题
欢迎来到博主的专栏:算法解析 博主ID:代码小号 文章目录 牛客网——【模板】01背包题目解析题目1算法原理题目1题解代码。问题2算法原理问题2题解代码01背包问题的滚动数组优化 牛客网——【模板】01背包 题目解析 关于I/O相关的东西博主就不多赘述了&a…...
vulkanscenegraph显示倾斜模型(5.8)-视景器编译
前言 上一章深入剖析了Vulkan渲染过程中的核心组件VkPipeline,以及vsg中针对图形渲染管线的封装GraphicsPipeline,本章将继续深入探讨视景器准备过程中的最后一章视景器编译,并进一步深入vsg中显存的传输机制,即vsg::TransferTask…...
21 天 Python 计划:MySQL索引机制从基础到应用
文章目录 一、引言二、MySQL索引机制概述2.1 MySQL索引的创建方式2.2 查询、删除、指定索引2.3 数据库索引的本质 三、MySQL的索引分类3.1 数据结构层次3.2 字段数量层次3.3 功能逻辑层次3.4 存储方式层次3.5 索引分类小结 四、MySQL其他索引的创建使用方式4.1 唯一索引的创建与…...
强化学习系统在复杂推理模型中的应用——以AReaL系统为例
目录 强化学习系统在复杂推理模型中的应用——以AReaL系统为例 1. 背景与系统设计目标 2. AReaL系统的主要技术特点 2.1 强化学习策略的引入 2.2 优化训练流程 2.3 泛化能力的提升 3. 实际应用场景 4. 总结与展望 强化学习系统在复杂推理模型中的应用——以AReaL系统为例…...
关于 微服务中服务注册与发现 的详细说明,涵盖主流框架/解决方案的对比、核心功能、配置示例及总结表格
以下是关于 微服务中服务注册与发现 的详细说明,涵盖主流框架/解决方案的对比、核心功能、配置示例及总结表格: 1. 服务注册与发现的核心概念 服务注册与发现是微服务架构的基础能力,主要解决以下问题: 服务注册:服…...
Tiny Cluster(1)——搭建树莓派小型计算集群
1 硬件说明 1.1 软硬件环境 k8s-master-0192.168.5.78Debian 12树莓派58G / 4核 / 64G TF卡 & 512G SSD控制节点k8s-worker-0192.168.5.48Debian 12树莓派4B4G / 4核 / 64G TF卡工作节点k8s-worker-1192.168.5.16Debian 11BTT-CB11G / 4核 / 64G TF卡工作节点 实现 K8s …...
【软考系统架构设计师】信息安全技术基础
1、 信息安全包括5个基本要素:机密性、完整性、可用性、可控性与可审查性。 机密性:确保信息不暴露给未授权的实体或进程。(采取加密措施) 完整性:只有得到允许的人才能修改数据,并且能够判断出数据是否已…...
CCF CSP 第35次(2024.09)(2_字符串变换_C++)(哈希表+getline)
CCF CSP 第35次(2024.09)(2_字符串变换_C) 解题思路:思路一(哈希表getline): 代码实现代码实现(思路一(哈希表getline)): …...
oracle怎么查看是否走了索引
SELECT * FROM CRM_STATION_APPEAL_RESULT WHERE COMPLAINT_ID ce1a1d8f-e2a2-4126-8cb7-14384cb24468; 这是查询语句,怎么看这个查询是否走了索引呢 EXPLAIN PLAN FOR SELECT * FROM CRM_STATION_APPEAL_RESULT WHERE COMPLAINT_ID ce1a1d8f-e2a2-4126-8cb7-14…...
八股系列(分布式与微服务)持续更新!
八股系列(分布式与微服务) 分布式系统的概念 分布式系统是由多个节点组成,节点之间通过网络协议传递数据,对外表现为一个统一的整体,一个节点可以是一台机器或一个进程;分布式系统的核心功能 资源共享&…...
19【动手学深度学习】卷积层
1. 从全连接到卷积 2. 图像卷积 3. 图形卷积代码 互相关操作 import torch from torch import nn from d2l import torch as d2ldef corr2d(X, K):"""计算2维互相关运算"""h, w K.shapeY torch.zeros((X.shape[0]-h1, X.shape[1]-w 1))for …...
Linux基础9
一、日志管理 > 日志配置文件: > > /var/log/messages #内核的消息以及各种服务的公共信息 > > /var/log/dmesg #系统启动过程信息 > > /var/log/cron #cron计划任务相关信息 > > /var…...
[创业之路-366]:投资尽职调查 - 尽调核心逻辑与核心影响因素:价值、估值、退出、风险、策略
目录 一、VC投资的本质是冒着不确定性风险进行买卖、生意,为了赚取高额回报 1、VC投资的核心本质 2、VC投资的运作机制 3、VC投资的风险与挑战 4、VC投资的底层逻辑 5、总结:VC投资的本质再定义 二、尽调核心逻辑 1、尽调的含义 2、尽调的逻辑方…...