1-GGML:看ctx是个什么东西
GGML
- ggml_context
- 定义
- 初始化
- OBJ
- 工程技巧
ggml_context
ggml_context在代码的主部分都存在,简单描述就是对内存的管理,本质是一个链表.
定义
struct ggml_context {size_t mem_size; // 内存大小void* mem_buffer; // 内存地址bool mem_buffer_owned;bool no_alloc;bool no_alloc_save; // this is used to save the no_alloc state when using scratch buffersint n_objects;// 开始结束 这是一个链表struct ggml_object * objects_begin;struct ggml_object * objects_end;struct ggml_scratch scratch;struct ggml_scratch scratch_save;
};
通过定义可以看到他其实就是一个链表的,存储了有多少个object,objects_begin和objects_end是链表的头和尾,
初始化
struct ggml_context * ggml_init(struct ggml_init_params params) {// ..................................// find non-used context in g_statestruct ggml_context * ctx = NULL;// 建立一个context指针for (int i = 0; i < GGML_MAX_CONTEXTS; i++) {// g_state 是全局的状态if (!g_state.contexts[i].used) { // 在全局里找一个没用的g_state.contexts[i].used = true; // 标记为使用ctx = &g_state.contexts[i].context;// 拿出来contextGGML_PRINT_DEBUG("%s: found unused context %d\n", __func__, i);break;// 跳出}}if (ctx == NULL) {// 为空,说明用完了GGML_PRINT_DEBUG("%s: no unused context found\n", __func__);ggml_critical_section_end();return NULL;}// allow to call ggml_init with 0 sizeif (params.mem_size == 0) {params.mem_size = GGML_MEM_ALIGN;}const size_t mem_size = params.mem_buffer ? params.mem_size : GGML_PAD(params.mem_size, GGML_MEM_ALIGN);// 往ctx里赋值*ctx = (struct ggml_context) {/*.mem_size =*/ mem_size,// 内存大小/*.mem_buffer =*/ params.mem_buffer ? params.mem_buffer : GGML_ALIGNED_MALLOC(mem_size),// 如果地址是空分配地址/*.mem_buffer_owned =*/ params.mem_buffer ? false : true,/*.no_alloc =*/ params.no_alloc,/*.no_alloc_save =*/ params.no_alloc,/*.n_objects =*/ 0,/*.objects_begin =*/ NULL,/*.objects_end =*/ NULL,/*.scratch =*/ { 0, 0, NULL, },/*.scratch_save =*/ { 0, 0, NULL, },};GGML_ASSERT(ctx->mem_buffer != NULL);GGML_ASSERT_ALIGNED(ctx->mem_buffer);// ..................................return ctx;
}
在ggml_init函数进行ctx的初始化,也就是从系统的空间里获得一个可用的ctx,主要关注这三个东西n_objects,objects_begin,objects_end.下面从一个obj的新建过程来看ctx是怎么被用的.
OBJ
obj是一个基本的单元,他在程序中的类型是枚举成这样子的
- 类型
enum ggml_object_type {GGML_OBJECT_TYPE_TENSOR,// tensorGGML_OBJECT_TYPE_GRAPH, // 计算图GGML_OBJECT_TYPE_WORK_BUFFER// 工作buff};
可以看出一个objec可以是tensor张量,计算图和buffer,也就基本上构成了整个工程的所有数据形式
- 构造
struct ggml_object {size_t offs;size_t size;struct ggml_object * next;// 指向下一个,链表元素enum ggml_object_type type;// 类型有张量、计算图、buff等char padding[4];// 填充?};
通过下面构建一个new_obj的过程可以看到的是offs偏移的是obj真正关联的数据.简单来说obj是个塑料袋子,offs是袋子里面真正想存的东西的.另外size也需要注意,下面的新建过程可以看到size是传入的大小经过取整-内存对齐后的大小.新建一个obj的语句是"ggml_new_object(ctx, GGML_OBJECT_TYPE_TENSOR, GGML_TENSOR_SIZE + obj_alloc_size)",可以看到其实size是指的也是袋子里的物品的size,像这个例子一样存的是tensor当然是tensor这个对象+tensor数据的大小和.
// 新建一个object
static struct ggml_object * ggml_new_object(struct ggml_context * ctx, enum ggml_object_type type, size_t size) {// always insert objects at the end of the context's memory poolstruct ggml_object * obj_cur = ctx->objects_end;// 拿到尾巴的objconst size_t cur_offs = obj_cur == NULL ? 0 : obj_cur->offs;// 内存块偏置const size_t cur_size = obj_cur == NULL ? 0 : obj_cur->size;// 内存块大小const size_t cur_end = cur_offs + cur_size;// 当前位置// align to GGML_MEM_ALIGNsize_t size_needed = GGML_PAD(size, GGML_MEM_ALIGN);char * const mem_buffer = ctx->mem_buffer;// 内存块// 新建一个objstruct ggml_object * const obj_new = (struct ggml_object *)(mem_buffer + cur_end);if (cur_end + size_needed + GGML_OBJECT_SIZE > ctx->mem_size) {GGML_PRINT("%s: not enough space in the context's memory pool (needed %zu, available %zu)\n",__func__, cur_end + size_needed, ctx->mem_size);assert(false);return NULL;}*obj_new = (struct ggml_object) {.offs = cur_end + GGML_OBJECT_SIZE,// obj的数据所放地址.size = size_needed, // 内存对齐后的大小.next = NULL, // 没有下一个.type = type,};GGML_ASSERT_ALIGNED(mem_buffer + obj_new->offs);if (obj_cur != NULL) {// 放入链表尾obj_cur->next = obj_new;} else {// 链表的头是空,放头部// this is the first object in this contextctx->objects_begin = obj_new;}ctx->objects_end = obj_new;//printf("%s: inserted new object at %zu, size = %zu\n", __func__, cur_end, obj_new->size);return obj_new;
}
可以看出ctx里的内存块的管理是依赖obj的,obj像一个一级索引,指示了这些数据是放在ctx中.同时通过next的设定把这些数据进行串联起来
工程技巧
- ggml是怎么进行内存对齐的
#define GGML_PAD(x, n) (((x) + (n) - 1) & ~((n) - 1))
稍微解释一下,x是size,n是对齐的位数,n是二进制的整数比如4/8/16这样,那么 ~((n) - 1))其实是生成一个掩码,低位为0,高位为1. ~是按位置取反运算符号.比如n是4 = b000100,那么 ~(4 -1 ) = ~ (b000011) = 0x111100,进而再&的肯定是4的整倍数了
