简介
本文主要想要讨论的是,是yolov3模型加载过程中,所涉及的数据结构。
流程
在main函数中,可以看到,首先调用的是;ggml_time_init(), 在ggml.c中可以找到它的定义,主要用于计时。
然后,初始化对象yolo_model,观察其数据结构发现,有一个ggml_context的数据结构,以及一个conv2d组成的vector。
然后, 创建对象yolo_params,并赋值。这里提供了模型的名称等信息。然后,我们可以通过输入的参数修改这些信息。
struct yolo_params {
float thresh = 0.5;
std::string model = "yolov3-tiny.gguf";
std::string fname_inp = "input.jpg";
std::string fname_out = "predictions.jpg";
};
在load_model函数中,我们可以通过模型的路径,将模型参数加载到yolo_model中,
这里对应的关键函数是gguf_init_from_model()。 这个函数的长度为300多行,实现了gguf模型的加载。这个过程详细说起来又是另一个i故事了。
static bool load_model(const std::string & fname, yolo_model & model) {
struct gguf_init_params params = {
/*.no_alloc =*/ false,
/*.ctx =*/ &model.ctx,
};
gguf_context * ctx = gguf_init_from_file(fname.c_str(), params);
if (!ctx) {
fprintf(stderr, "%s: gguf_init_from_file() failed\n", __func__);
return false;
}
model.width = 416;
model.height = 416;
model.conv2d_layers.resize(13);
model.conv2d_layers[7].padding = 0;
model.conv2d_layers[9].padding = 0;
model.conv2d_layers[9].batch_normalize = false;
model.conv2d_layers[9].activate = false;
model.conv2d_layers[10].padding = 0;
model.conv2d_layers[12].padding = 0;
model.conv2d_layers[12].batch_normalize = false;
model.conv2d_layers[12].activate = false;
for (int i = 0; i < (int)model.conv2d_layers.size(); i++) {
char name[256];
snprintf(name, sizeof(name), "l%d_weights", i);
model.conv2d_layers[i].weights = ggml_get_tensor(model.ctx, name);
snprintf(name, sizeof(name), "l%d_biases", i);
model.conv2d_layers[i].biases = ggml_get_tensor(model.ctx, name);
if (model.conv2d_layers[i].batch_normalize) {
snprintf(name, sizeof(name), "l%d_scales", i);
model.conv2d_layers[i].scales = ggml_get_tensor(model.ctx, name);
snprintf(name, sizeof(name), "l%d_rolling_mean", i);
model.conv2d_layers[i].rolling_mean = ggml_get_tensor(model.ctx, name);
snprintf(name, sizeof(name), "l%d_rolling_variance", i);
model.conv2d_layers[i].rolling_variance = ggml_get_tensor(model.ctx, name);
}
}
return true;
}
在完成了gguf数据的读取后,模型的每一个卷积层的参数被读取,值得注意的是,这里卷积层的层数为13,是被提前定义,而不是读取到的,同样,输入的大小也是提前定义好的。读取的参数类型包括:weights, bias, scales,rolling_mean, rolling variance。
我们再来看看函数ggml_get_tensor的实现。
其实现可以在src/ggml.c中找到。通过对比tensor的名称,我们加载模型参数中对应的tensor。
struct ggml_tensor * ggml_get_tensor(struct ggml_context * ctx, const char * name) {
struct ggml_object * obj = ctx->objects_begin;
char * const mem_buffer = ctx->mem_buffer;
while (obj != NULL) {
if (obj->type == GGML_OBJECT_TENSOR) {
struct ggml_tensor * cur = (struct ggml_tensor *)(mem_buffer + obj->offs);
if (strcmp(cur->name, name) == 0) {
return cur;
}
}
obj = obj->next;
}
return NULL;
}
我们再来看一下ggml_tensor的数据结构。
ggml_backend_type定义了tensor是通过gpu还是cpu计算。此外,还定义参数的数量等·参数。
// n-dimensional tensor
struct ggml_tensor {
enum ggml_type type;
enum ggml_backend_type backend;
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)];
bool is_param;
struct ggml_tensor * grad;
struct ggml_tensor * src[GGML_MAX_SRC];
// performance
int perf_runs;
int64_t perf_cycles;
int64_t perf_time_us;
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.cu
char padding[8];
};
总结
模型参数的记载到此基本讲完,读取的这些参数的作用,只有结合具体的推理,才能更好的理解,这也是下一章的目标。