mirror of
https://github.com/Tencent/ncnn.git
synced 2026-10-18 05:36:58 +08:00
1499 lines
51 KiB
C++
1499 lines
51 KiB
C++
// Copyright 2026 Tencent
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
#include <stdio.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
|
|
#include "datareader.h"
|
|
#include "layer_type.h"
|
|
#include "modelbin.h"
|
|
#include "net.h"
|
|
#include "paramdict.h"
|
|
#include "testutil.h"
|
|
|
|
#include <errno.h>
|
|
|
|
#if defined _WIN32
|
|
#include <process.h>
|
|
#endif
|
|
|
|
struct LayerState
|
|
{
|
|
LayerState()
|
|
: created(0), destroyed(0), pipeline_created(0), pipeline_destroyed(0), pipeline_destroyed_before_param(0), load_param_ret(0), cpu_load_param_ret(0), one_blob_only(false), support_vulkan(false), fail_create(false)
|
|
{
|
|
}
|
|
|
|
int created;
|
|
int destroyed;
|
|
int pipeline_created;
|
|
int pipeline_destroyed;
|
|
int pipeline_destroyed_before_param;
|
|
int load_param_ret;
|
|
int cpu_load_param_ret;
|
|
bool one_blob_only;
|
|
bool support_vulkan;
|
|
bool fail_create;
|
|
};
|
|
|
|
class TestLayer : public ncnn::Layer
|
|
{
|
|
public:
|
|
TestLayer(LayerState* _state, bool _support_vulkan, int _load_param_ret)
|
|
: state(_state), load_param_ret(_load_param_ret), param_loaded(false)
|
|
{
|
|
one_blob_only = state->one_blob_only;
|
|
support_vulkan = _support_vulkan;
|
|
}
|
|
|
|
virtual int load_param(const ncnn::ParamDict&)
|
|
{
|
|
support_vulkan = false;
|
|
param_loaded = load_param_ret == 0;
|
|
return load_param_ret;
|
|
}
|
|
|
|
virtual int create_pipeline(const ncnn::Option&)
|
|
{
|
|
state->pipeline_created++;
|
|
return 0;
|
|
}
|
|
|
|
virtual int destroy_pipeline(const ncnn::Option&)
|
|
{
|
|
state->pipeline_destroyed++;
|
|
if (!param_loaded)
|
|
state->pipeline_destroyed_before_param++;
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
LayerState* state;
|
|
int load_param_ret;
|
|
bool param_loaded;
|
|
};
|
|
|
|
static ncnn::Layer* create_test_layer(void* userdata)
|
|
{
|
|
LayerState* state = (LayerState*)userdata;
|
|
if (state->fail_create)
|
|
return 0;
|
|
|
|
state->created++;
|
|
return new TestLayer(state, state->support_vulkan, state->load_param_ret);
|
|
}
|
|
|
|
static void destroy_test_layer(ncnn::Layer* layer, void* userdata)
|
|
{
|
|
LayerState* state = (LayerState*)userdata;
|
|
state->destroyed++;
|
|
delete layer;
|
|
}
|
|
|
|
static ncnn::Layer* create_null_layer(void*)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static ncnn::Layer* create_layer_once(void* userdata)
|
|
{
|
|
LayerState* state = (LayerState*)userdata;
|
|
ncnn::Layer* layer = create_test_layer(userdata);
|
|
state->fail_create = true;
|
|
return layer;
|
|
}
|
|
|
|
static ncnn::Layer* create_layer_with_cpu_fallback(void* userdata)
|
|
{
|
|
LayerState* state = (LayerState*)userdata;
|
|
ncnn::Layer* layer = create_test_layer(userdata);
|
|
// configure the next instance for cpu fallback
|
|
state->support_vulkan = false;
|
|
state->load_param_ret = state->cpu_load_param_ret;
|
|
return layer;
|
|
}
|
|
|
|
static bool empty_net(const ncnn::Net& net)
|
|
{
|
|
return net.layers().empty() && net.blobs().empty() && net.input_indexes().empty() && net.output_indexes().empty()
|
|
&& net.input_names().empty() && net.output_names().empty();
|
|
}
|
|
|
|
// memory readers without a length cannot detect truncated binary input
|
|
class BoundedNetReader : public ncnn::DataReader
|
|
{
|
|
public:
|
|
BoundedNetReader(const unsigned char* p, size_t size)
|
|
: data(p), remaining(size)
|
|
{
|
|
}
|
|
|
|
virtual size_t read(void* buf, size_t size) const
|
|
{
|
|
if (size > remaining) size = remaining;
|
|
if (size != 0)
|
|
{
|
|
memcpy(buf, data, size);
|
|
data += size;
|
|
remaining -= size;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
private:
|
|
mutable const unsigned char* data;
|
|
mutable size_t remaining;
|
|
};
|
|
|
|
static int load_binary(ncnn::Net& net, const unsigned char* data, size_t size)
|
|
{
|
|
BoundedNetReader dr(data, size);
|
|
return net.load_param_bin(dr);
|
|
}
|
|
|
|
static void append_int(std::vector<unsigned char>& data, int value)
|
|
{
|
|
const unsigned int v = (unsigned int)value;
|
|
for (int i = 0; i < 4; i++)
|
|
data.push_back((unsigned char)(v >> (i * 8)));
|
|
}
|
|
|
|
static std::vector<unsigned char> binary_header(int layers, int blobs)
|
|
{
|
|
std::vector<unsigned char> data;
|
|
append_int(data, 7767517);
|
|
append_int(data, layers);
|
|
append_int(data, blobs);
|
|
return data;
|
|
}
|
|
|
|
static void append_layer_header(std::vector<unsigned char>& data, int type, int bottoms, int tops)
|
|
{
|
|
append_int(data, type);
|
|
append_int(data, bottoms);
|
|
append_int(data, tops);
|
|
}
|
|
|
|
static int check_text(const char* text, int expected_ret)
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Test", create_test_layer, destroy_test_layer, &state);
|
|
net.register_custom_layer("Input", create_test_layer, destroy_test_layer, &state);
|
|
|
|
int ret = net.load_param_mem(text);
|
|
if (ret != expected_ret || (ret != 0 && !empty_net(net)))
|
|
{
|
|
fprintf(stderr, "test_net text failed ret=%d expected=%d\n%s\n", ret, expected_ret, text);
|
|
return -1;
|
|
}
|
|
|
|
net.clear();
|
|
if (!empty_net(net) || state.created != state.destroyed || state.pipeline_destroyed_before_param != 0
|
|
|| state.pipeline_destroyed != (expected_ret == 0 ? state.created : 0))
|
|
{
|
|
fprintf(stderr, "test_net text cleanup failed created=%d destroyed=%d pipelines=%d pipelines_before_param=%d\n%s\n", state.created, state.destroyed, state.pipeline_destroyed, state.pipeline_destroyed_before_param, text);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_binary(const char* name, const std::vector<unsigned char>& data, size_t size, int expected_ret)
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Test", create_test_layer, destroy_test_layer, &state);
|
|
net.register_custom_layer("Input", create_test_layer, destroy_test_layer, &state);
|
|
|
|
int ret = load_binary(net, &data[0], size);
|
|
if (ret != expected_ret || (ret != 0 && !empty_net(net)))
|
|
{
|
|
fprintf(stderr, "test_net binary %s failed size=%zu ret=%d expected=%d\n", name, size, ret, expected_ret);
|
|
return -1;
|
|
}
|
|
|
|
net.clear();
|
|
if (!empty_net(net) || state.created != state.destroyed || state.pipeline_destroyed_before_param != 0
|
|
|| state.pipeline_destroyed != (expected_ret == 0 ? state.created : 0))
|
|
{
|
|
fprintf(stderr, "test_net binary %s cleanup failed created=%d destroyed=%d pipelines=%d pipelines_before_param=%d\n", name, state.created, state.destroyed, state.pipeline_destroyed, state.pipeline_destroyed_before_param);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_binary_truncated()
|
|
{
|
|
const int custom = ncnn::LayerType::CustomBit;
|
|
for (int builtin = 0; builtin < 2; builtin++)
|
|
{
|
|
const int type = builtin ? ncnn::LayerType::Input : custom;
|
|
const char* layer_name = builtin ? "overwritten layer" : "custom layer";
|
|
std::vector<unsigned char> valid = binary_header(2, 2);
|
|
append_layer_header(valid, type, 0, 1);
|
|
append_int(valid, 0);
|
|
append_int(valid, -233);
|
|
append_int(valid, type);
|
|
append_int(valid, 1);
|
|
append_int(valid, 1);
|
|
append_int(valid, 0);
|
|
append_int(valid, 1);
|
|
append_int(valid, -233);
|
|
for (size_t n = 0; n < valid.size(); n++)
|
|
{
|
|
if (check_binary(layer_name, valid, n, -1))
|
|
return -1;
|
|
}
|
|
if (check_binary(layer_name, valid, valid.size(), 0))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_binary_dependencies()
|
|
{
|
|
const int custom = ncnn::LayerType::CustomBit;
|
|
struct Case
|
|
{
|
|
const char* name;
|
|
int layers;
|
|
int blobs;
|
|
int values[24];
|
|
int count;
|
|
int expected_ret;
|
|
};
|
|
const Case cases[] = {
|
|
{"forward reference", 2, 2, {custom, 1, 1, 1, 0, -233, custom, 0, 1, 1, -233}, 11, 0},
|
|
{"shared dependency", 4, 4, {custom, 2, 1, 1, 2, 0, -233, custom, 1, 1, 3, 1, -233, custom, 1, 1, 3, 2, -233, custom, 0, 1, 3, -233}, 24, 0},
|
|
{"repeated dependency", 2, 2, {custom, 2, 1, 1, 1, 0, -233, custom, 0, 1, 1, -233}, 12, 0},
|
|
{"external input", 1, 2, {custom, 1, 1, 0, 1, -233}, 6, 0}
|
|
};
|
|
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
|
|
{
|
|
const Case& c = cases[i];
|
|
std::vector<unsigned char> data;
|
|
append_int(data, 7767517);
|
|
append_int(data, c.layers);
|
|
append_int(data, c.blobs);
|
|
for (int j = 0; j < c.count; j++)
|
|
append_int(data, c.values[j]);
|
|
|
|
if (check_binary(c.name, data, data.size(), c.expected_ret))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_binary_deep_dependencies(int cyclic)
|
|
{
|
|
// only test that cycle detection handles deep graphs without recursive traversal
|
|
const int layer_count = 16384;
|
|
std::vector<unsigned char> data;
|
|
append_int(data, 7767517);
|
|
append_int(data, layer_count);
|
|
append_int(data, layer_count + 1);
|
|
for (int i = 0; i < layer_count; i++)
|
|
{
|
|
append_int(data, ncnn::LayerType::CustomBit);
|
|
append_int(data, 1);
|
|
append_int(data, 1);
|
|
append_int(data, cyclic && i == layer_count - 1 ? 0 : i + 1);
|
|
append_int(data, i);
|
|
append_int(data, -233);
|
|
}
|
|
if (check_binary(cyclic ? "deep cycle" : "deep chain", data, data.size(), cyclic ? -1 : 0))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_layer_load_param_error()
|
|
{
|
|
const char* layer_types[] = {"Test", "Input"};
|
|
const int type_indexes[] = {ncnn::LayerType::CustomBit, ncnn::LayerType::Input};
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
LayerState state;
|
|
state.load_param_ret = -1;
|
|
ncnn::Net net;
|
|
net.register_custom_layer(layer_types[i], create_test_layer, destroy_test_layer, &state);
|
|
|
|
char text[128];
|
|
snprintf(text, sizeof(text), "7767517\n1 1\n%s t 0 1 out\n", layer_types[i]);
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, type_indexes[i], 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem(text);
|
|
if (ret != -1 || !empty_net(net) || state.created != 1 || state.destroyed != 1 || state.pipeline_destroyed != 0)
|
|
{
|
|
fprintf(stderr, "test_net load_param error cleanup failed type=%s binary=%d ret=%d created=%d destroyed=%d pipelines=%d\n", layer_types[i], binary, ret, state.created, state.destroyed, state.pipeline_destroyed);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_shape_hints(const ncnn::Net& net, const int shapes[][5])
|
|
{
|
|
const ncnn::Layer* layer = net.layers()[0];
|
|
for (size_t i = 0; i < layer->tops.size(); i++)
|
|
{
|
|
const ncnn::Mat& shape = net.blobs()[layer->tops[i]].shape;
|
|
const int* expected = shapes[i];
|
|
if (shape.dims != expected[0] || shape.w != expected[1] || shape.h != expected[2]
|
|
|| shape.d != expected[3] || shape.c != expected[4])
|
|
{
|
|
fprintf(stderr, "test_net shape hint failed top=%zu shape=%d,%d,%d,%d,%d expected=%d,%d,%d,%d,%d\n",
|
|
i, shape.dims, shape.w, shape.h, shape.d, shape.c,
|
|
expected[0], expected[1], expected[2], expected[3], expected[4]);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_shape_hints()
|
|
{
|
|
struct Case
|
|
{
|
|
const char* text;
|
|
int count;
|
|
int values[10];
|
|
int tops;
|
|
// dims, w, h, d, c for each top
|
|
int shapes[2][5];
|
|
};
|
|
const Case cases[] = {
|
|
{"4,1,7,0,0", 4, {1, 7, 0, 0}, 1, {{1, 7, 1, 1, 1}}},
|
|
{"4,2,7,6,0", 4, {2, 7, 6, 0}, 1, {{2, 7, 6, 1, 1}}},
|
|
{"4,3,7,6,5", 4, {3, 7, 6, 5}, 1, {{3, 7, 6, 1, 5}}},
|
|
{"5,3,7,6,1,5", 5, {3, 7, 6, 1, 5}, 1, {{3, 7, 6, 1, 5}}},
|
|
{"5,4,7,6,5,4", 5, {4, 7, 6, 5, 4}, 1, {{4, 7, 6, 5, 4}}},
|
|
{"8,1,7,0,0,2,6,5,0", 8, {1, 7, 0, 0, 2, 6, 5, 0}, 2, {{1, 7, 1, 1, 1}, {2, 6, 5, 1, 1}}},
|
|
{"4,0,0,0,0", 4, {0, 0, 0, 0}, 1, {{0}}},
|
|
{"4,1,0,0,0", 4, {1, 0, 0, 0}, 1, {{1, 0, 1, 1, 1}}},
|
|
{"1,4", 1, {4}, 0, {{0}}}
|
|
};
|
|
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
|
|
{
|
|
const Case& c = cases[i];
|
|
char text[512];
|
|
const char* top_names[] = {"", " out", " out out2"};
|
|
snprintf(text, sizeof(text), "7767517\n1 2\nTest t 0 %d%s -23330=%s\n", c.tops, top_names[c.tops], c.text);
|
|
std::vector<unsigned char> data = binary_header(1, 2);
|
|
append_layer_header(data, ncnn::LayerType::CustomBit, 0, c.tops);
|
|
for (int j = 0; j < c.tops; j++) append_int(data, j);
|
|
append_int(data, -23330);
|
|
append_int(data, c.count);
|
|
for (int j = 0; j < c.count; j++) append_int(data, c.values[j]);
|
|
append_int(data, -233);
|
|
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Test", create_test_layer, destroy_test_layer, &state);
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem(text);
|
|
if (ret != 0)
|
|
{
|
|
fprintf(stderr, "test_net shape hint load failed binary=%d hints=%s ret=%d\n", binary, c.text, ret);
|
|
return -1;
|
|
}
|
|
if (check_shape_hints(net, c.shapes) != 0)
|
|
{
|
|
fprintf(stderr, "test_net shape hint failed binary=%d hints=%s\n", binary, c.text);
|
|
return -1;
|
|
}
|
|
net.clear();
|
|
if (!empty_net(net) || state.created != state.destroyed || state.pipeline_destroyed != state.created || state.pipeline_destroyed_before_param != 0)
|
|
{
|
|
fprintf(stderr, "test_net shape hint cleanup failed binary=%d hints=%s\n", binary, c.text);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
if (check_text("7767517\n1 1\nTest t 0 1 out 30=3,7,6,5\n", 0))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_reload()
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Input", create_test_layer, destroy_test_layer, &state);
|
|
|
|
const char* text = "7767517\n1 1\nInput t 0 1 out\n";
|
|
int ret = net.load_param_mem(text);
|
|
if (ret != 0 || net.input_names().size() != 1 || net.output_names().size() != 1)
|
|
{
|
|
fprintf(stderr, "test_net initial load failed ret=%d inputs=%zu outputs=%zu\n", ret, net.input_names().size(), net.output_names().size());
|
|
return -1;
|
|
}
|
|
|
|
ret = net.load_param_mem(text);
|
|
if (ret != 0 || state.created != 2 || state.destroyed != 1)
|
|
{
|
|
fprintf(stderr, "test_net reload failed ret=%d created=%d destroyed=%d\n", ret, state.created, state.destroyed);
|
|
return -1;
|
|
}
|
|
|
|
// truncate the bottom name to exercise failed reload cleanup
|
|
ret = net.load_param_mem("7767517\n1 1\nInput t 1 0");
|
|
if (ret != -1 || !empty_net(net) || state.created != state.destroyed)
|
|
{
|
|
fprintf(stderr, "test_net failed reload cleanup failed ret=%d created=%d destroyed=%d\n", ret, state.created, state.destroyed);
|
|
return -1;
|
|
}
|
|
|
|
ret = net.load_param_mem(text);
|
|
if (ret != 0)
|
|
{
|
|
fprintf(stderr, "test_net reload after error failed ret=%d\n", ret);
|
|
return -1;
|
|
}
|
|
|
|
net.clear();
|
|
if (!empty_net(net) || state.created != state.destroyed)
|
|
{
|
|
fprintf(stderr, "test_net clear after reload failed created=%d destroyed=%d\n", state.created, state.destroyed);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_name_length()
|
|
{
|
|
char name[257];
|
|
memset(name, 'a', 256);
|
|
name[255] = 0;
|
|
char text[1024];
|
|
snprintf(text, sizeof(text), "7767517\n1 1\nTest %s 0 1 %s\n", name, name);
|
|
if (check_text(text, 0))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_integer_length(int length)
|
|
{
|
|
char token[33];
|
|
memset(token, '0', length);
|
|
token[length - 1] = '1';
|
|
token[length] = 0;
|
|
char text[256];
|
|
snprintf(text, sizeof(text), "7767517\n%s 1\nTest t 0 1 out\n", token);
|
|
if (check_text(text, length <= 31 ? 0 : -1))
|
|
return -1;
|
|
snprintf(text, sizeof(text), "7767517\n1 1\nTest t 0 %s out\n", token);
|
|
if (check_text(text, length <= 31 ? 0 : -1))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_repeated_blob_references()
|
|
{
|
|
// duplicate references are legal even when the count exceeds blob_count
|
|
if (check_text("7767517\n1 1\nTest t 2 0 in in\n", 0))
|
|
return -1;
|
|
if (check_text("7767517\n1 2\nTest t 3 0 a a b\n", 0)
|
|
|| check_text("7767517\n1 2\nTest t 3 0 a b a\n", 0))
|
|
return -1;
|
|
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, ncnn::LayerType::CustomBit, 2, 0);
|
|
append_int(data, 0);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
if (check_binary("repeated blob references", data, data.size(), 0))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_builtin_without_override()
|
|
{
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
ncnn::Net net;
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, ncnn::LayerType::Input, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem("7767517\n1 1\nInput t 0 1 out\n");
|
|
if (ret != 0)
|
|
{
|
|
fprintf(stderr, "test_net builtin without override failed binary=%d ret=%d\n", binary, ret);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_null_creators()
|
|
{
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
for (int builtin = 0; builtin < 2; builtin++)
|
|
{
|
|
for (int null_function = 0; null_function < 2; null_function++)
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
const int typeindex = builtin ? ncnn::LayerType::Input : ncnn::LayerType::CustomBit;
|
|
ncnn::layer_creator_func creator = null_function ? 0 : create_null_layer;
|
|
if (binary)
|
|
net.register_custom_layer(typeindex, creator, destroy_test_layer, &state);
|
|
else
|
|
net.register_custom_layer(builtin ? "Input" : "Test", creator, destroy_test_layer, &state);
|
|
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, typeindex, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
const char* text = builtin ? "7767517\n1 1\nInput t 0 1 out\n" : "7767517\n1 1\nTest t 0 1 out\n";
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem(text);
|
|
if (ret != -1 || !empty_net(net) || state.created != 0 || state.destroyed != 0)
|
|
{
|
|
fprintf(stderr, "test_net null creator failed binary=%d builtin=%d null_function=%d ret=%d created=%d destroyed=%d\n", binary, builtin, null_function, ret, state.created, state.destroyed);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_shape_layout()
|
|
{
|
|
const char* text = "7767517\n2 2\nInput in 0 1 in -23330=5,4,7,6,5,4\nTest t 1 1 in out -23330=4,3,3,2,1\n";
|
|
std::vector<unsigned char> data = binary_header(2, 2);
|
|
append_layer_header(data, ncnn::LayerType::Input, 0, 1);
|
|
const int values[] = {0, -23330, 5, 4, 7, 6, 5, 4, -233, ncnn::LayerType::CustomBit, 1, 1, 0, 1, -23330, 4, 3, 3, 2, 1, -233};
|
|
for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) append_int(data, values[i]);
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Test", create_test_layer, destroy_test_layer, &state);
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem(text);
|
|
if (ret != 0)
|
|
{
|
|
fprintf(stderr, "test_net shape layout load failed binary=%d ret=%d\n", binary, ret);
|
|
return -1;
|
|
}
|
|
|
|
const ncnn::Mat& in = net.layers()[1]->bottom_shapes[0];
|
|
const ncnn::Mat& out = net.layers()[1]->top_shapes[0];
|
|
if (in.dims != 4 || in.w != 7 || in.h != 6 || in.d != 5 || in.c != 4
|
|
|| out.dims != 3 || out.w != 3 || out.h != 2 || out.d != 1 || out.c != 1)
|
|
{
|
|
fprintf(stderr, "test_net shape layout binary=%d failed\n", binary);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if NCNN_BF16
|
|
class StoragePackingLayer : public ncnn::Layer
|
|
{
|
|
public:
|
|
StoragePackingLayer()
|
|
{
|
|
one_blob_only = true;
|
|
support_packing = true;
|
|
}
|
|
|
|
virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option&) const
|
|
{
|
|
top_blob = bottom_blob;
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
static ncnn::Layer* create_storage_packing_layer(void*)
|
|
{
|
|
return new StoragePackingLayer;
|
|
}
|
|
|
|
static int test_storage_packing(int storage_type)
|
|
{
|
|
if (storage_type == 2)
|
|
{
|
|
bool support_fp16 = false;
|
|
#if NCNN_ARM82
|
|
support_fp16 = support_fp16 || ncnn::cpu_support_arm_asimdhp();
|
|
#endif
|
|
#if NCNN_VFPV4
|
|
support_fp16 = support_fp16 || ncnn::cpu_support_arm_vfpv4();
|
|
#endif
|
|
#if NCNN_ZFH
|
|
support_fp16 = support_fp16 || ncnn::cpu_support_riscv_zvfh() || (!ncnn::cpu_support_riscv_v() && ncnn::cpu_support_riscv_zfh());
|
|
#endif
|
|
if (!support_fp16)
|
|
return 0;
|
|
}
|
|
|
|
ncnn::Net net;
|
|
net.opt.num_threads = 1;
|
|
net.opt.use_packing_layout = true;
|
|
net.opt.use_fp16_storage = storage_type == 2;
|
|
net.opt.use_bf16_storage = storage_type == 4;
|
|
#if NCNN_RVV || NCNN_XTHEADVECTOR
|
|
if (!ncnn::cpu_support_riscv_v() && !ncnn::cpu_support_riscv_xtheadvector())
|
|
net.opt.use_packing_layout = false;
|
|
#endif
|
|
net.register_custom_layer("StoragePacking", create_storage_packing_layer);
|
|
const char* param = "7767517\n2 2\nInput in 0 1 in\nStoragePacking fp32 1 1 in out\n";
|
|
BoundedNetReader dr(0, 0);
|
|
if (net.load_param_mem(param) != 0 || net.load_model(dr) != 0)
|
|
{
|
|
fprintf(stderr, "test_net storage packing load failed storage_type=%d\n", storage_type);
|
|
return -1;
|
|
}
|
|
|
|
ncnn::Mat in(5, 3, 128);
|
|
for (int q = 0; q < in.c; q++)
|
|
{
|
|
float* p = in.channel(q);
|
|
for (int i = 0; i < in.w * in.h; i++)
|
|
p[i] = (i - 7) * 0.25f + (q % 8);
|
|
}
|
|
|
|
ncnn::Mat input;
|
|
if (storage_type == 2)
|
|
ncnn::cast_float32_to_float16(in, input, net.opt);
|
|
else
|
|
ncnn::cast_float32_to_bfloat16(in, input, net.opt);
|
|
|
|
ncnn::Extractor ref = net.create_extractor();
|
|
ncnn::Mat expected;
|
|
if (ref.input("in", in) != 0 || ref.extract("out", expected, 1) != 0)
|
|
{
|
|
fprintf(stderr, "test_net storage packing reference failed storage_type=%d\n", storage_type);
|
|
return -1;
|
|
}
|
|
|
|
ncnn::Extractor ex = net.create_extractor();
|
|
ncnn::Mat out;
|
|
int ret = ex.input("in", input);
|
|
if (ret == 0)
|
|
ret = ex.extract("out", out, 1);
|
|
if (ret != 0 || out.elembits() != 32 || out.elempack != expected.elempack || CompareMat(in, out, 0.f) != 0)
|
|
{
|
|
fprintf(stderr, "test_net storage packing failed storage_type=%d ret=%d elembits=%d elempack=%d expected=%d\n", storage_type, ret, out.elembits(), out.elempack, expected.elempack);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif // NCNN_BF16
|
|
|
|
static int test_recreate_layer()
|
|
{
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
for (int builtin = 0; builtin < 2; builtin++)
|
|
{
|
|
for (int fail = 0; fail < 2; fail++)
|
|
{
|
|
LayerState state;
|
|
state.support_vulkan = true;
|
|
state.cpu_load_param_ret = fail ? -1 : 0;
|
|
ncnn::Net net;
|
|
net.register_custom_layer(builtin ? "Input" : "Test", create_layer_with_cpu_fallback, destroy_test_layer, &state);
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, builtin ? ncnn::LayerType::Input : ncnn::LayerType::CustomBit, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
const char* text = builtin ? "7767517\n1 1\nInput t 0 1 out\n" : "7767517\n1 1\nTest t 0 1 out\n";
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem(text);
|
|
if (ret != (fail ? -1 : 0) || state.created != 2 || state.destroyed != (fail ? 2 : 1) || state.pipeline_destroyed != 0 || (fail && !empty_net(net)))
|
|
{
|
|
fprintf(stderr, "test_net recreate binary=%d builtin=%d fail=%d ret=%d created=%d destroyed=%d pipelines=%d\n", binary, builtin, fail, ret, state.created, state.destroyed, state.pipeline_destroyed);
|
|
return -1;
|
|
}
|
|
net.clear();
|
|
if (state.destroyed != 2 || state.pipeline_destroyed != (fail ? 0 : 1) || state.pipeline_destroyed_before_param != 0)
|
|
{
|
|
fprintf(stderr, "test_net recreate cleanup failed binary=%d builtin=%d fail=%d destroyed=%d pipelines=%d pipelines_before_param=%d\n", binary, builtin, fail, state.destroyed, state.pipeline_destroyed, state.pipeline_destroyed_before_param);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int test_recreate_layer_null_creator()
|
|
{
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
for (int builtin = 0; builtin < 2; builtin++)
|
|
{
|
|
LayerState state;
|
|
state.support_vulkan = true;
|
|
ncnn::Net net;
|
|
const int typeindex = builtin ? ncnn::LayerType::Input : ncnn::LayerType::CustomBit;
|
|
if (binary)
|
|
net.register_custom_layer(typeindex, create_layer_once, destroy_test_layer, &state);
|
|
else
|
|
net.register_custom_layer(builtin ? "Input" : "Test", create_layer_once, destroy_test_layer, &state);
|
|
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, typeindex, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
const char* text = builtin ? "7767517\n1 1\nInput t 0 1 out\n" : "7767517\n1 1\nTest t 0 1 out\n";
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem(text);
|
|
if (ret != -1 || !empty_net(net) || state.created != 1 || state.destroyed != 1 || state.pipeline_destroyed != 0)
|
|
{
|
|
fprintf(stderr, "test_net null cpu creator failed binary=%d builtin=%d ret=%d created=%d destroyed=%d pipelines=%d\n", binary, builtin, ret, state.created, state.destroyed, state.pipeline_destroyed);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_external_input()
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Test", create_test_layer, destroy_test_layer, &state);
|
|
if (net.load_param_mem("7767517\n1 1\nTest t 1 0 in\n") != 0)
|
|
{
|
|
fprintf(stderr, "test_net external input load failed\n");
|
|
return -1;
|
|
}
|
|
|
|
ncnn::Extractor ex = net.create_extractor();
|
|
ncnn::Mat out;
|
|
int ret = ex.extract(0, out);
|
|
if (ret != -1)
|
|
{
|
|
fprintf(stderr, "test_net missing input ret=%d\n", ret);
|
|
return -1;
|
|
}
|
|
ncnn::Mat in(1);
|
|
in[0] = 42.f;
|
|
int input_ret = ex.input(0, in);
|
|
ret = ex.extract(0, out);
|
|
if (input_ret || ret || out.empty() || out[0] != 42.f)
|
|
{
|
|
fprintf(stderr, "test_net external input input_ret=%d ret=%d dims=%d value=%f\n", input_ret, ret, out.dims, out.empty() ? 0.f : out[0]);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int test_binary_layer_types()
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer(ncnn::LayerType::CustomBit | 512, create_test_layer, destroy_test_layer, &state);
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, 512, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
int ret = load_binary(net, &data[0], data.size());
|
|
if (ret != -1 || state.created != 0 || !empty_net(net))
|
|
{
|
|
fprintf(stderr, "test_net untagged custom layer failed ret=%d created=%d\n", ret, state.created);
|
|
return -1;
|
|
}
|
|
|
|
data = binary_header(1, 1);
|
|
append_layer_header(data, ncnn::LayerType::CustomBit | 512, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
ret = load_binary(net, &data[0], data.size());
|
|
if (ret != 0 || state.created != 1)
|
|
{
|
|
fprintf(stderr, "test_net tagged custom layer failed ret=%d created=%d\n", ret, state.created);
|
|
return -1;
|
|
}
|
|
|
|
net.clear();
|
|
if (state.destroyed != 1 || !empty_net(net))
|
|
{
|
|
fprintf(stderr, "test_net tagged custom layer cleanup failed destroyed=%d\n", state.destroyed);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_magic_mismatch()
|
|
{
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Input", create_test_layer, destroy_test_layer, &state);
|
|
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, ncnn::LayerType::Input, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem("7767517\n1 1\nInput t 0 1 out\n");
|
|
if (ret != 0 || net.input_indexes().size() != 1 || net.output_indexes().size() != 1)
|
|
{
|
|
fprintf(stderr, "test_net magic mismatch setup failed binary=%d ret=%d\n", binary, ret);
|
|
return -1;
|
|
}
|
|
|
|
data[0] = 0;
|
|
ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem("0\n1 1\nInput t 0 1 out\n");
|
|
if (ret != -1 || !empty_net(net) || state.created != 1 || state.destroyed != 1 || state.pipeline_destroyed != 1 || state.pipeline_destroyed_before_param != 0)
|
|
{
|
|
fprintf(stderr, "test_net magic mismatch failed binary=%d ret=%d created=%d destroyed=%d pipelines=%d\n", binary, ret, state.created, state.destroyed, state.pipeline_destroyed);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_feature_mask()
|
|
{
|
|
const int masks[] = {0, 1, 2, 17, 128, 255};
|
|
for (size_t i = 0; i < sizeof(masks) / sizeof(masks[0]); i++)
|
|
{
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Test", create_test_layer, destroy_test_layer, &state);
|
|
|
|
char text[128];
|
|
snprintf(text, sizeof(text), "7767517\n1 1\nTest t 0 1 out 31=%d\n", masks[i]);
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, ncnn::LayerType::CustomBit, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, 31);
|
|
append_int(data, masks[i]);
|
|
append_int(data, -233);
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem(text);
|
|
if (ret != 0 || net.layers().size() != 1 || net.layers()[0]->featmask != masks[i])
|
|
{
|
|
fprintf(stderr, "test_net feature mask failed binary=%d mask=%d ret=%d\n", binary, masks[i], ret);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_pipeline_lifecycle()
|
|
{
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
LayerState state;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Test", create_test_layer, destroy_test_layer, &state);
|
|
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, ncnn::LayerType::CustomBit, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem("7767517\n1 1\nTest t 0 1 out\n");
|
|
if (ret != 0 || state.pipeline_created != 0 || state.pipeline_destroyed != 0)
|
|
{
|
|
fprintf(stderr, "test_net pipeline load_param failed binary=%d ret=%d\n", binary, ret);
|
|
return -1;
|
|
}
|
|
|
|
BoundedNetReader dr(&data[0], 0);
|
|
ret = net.load_model(dr);
|
|
if (ret != 0 || state.pipeline_created != 1 || state.pipeline_destroyed != 0)
|
|
{
|
|
fprintf(stderr, "test_net pipeline load_model failed binary=%d ret=%d created=%d destroyed=%d\n", binary, ret, state.pipeline_created, state.pipeline_destroyed);
|
|
return -1;
|
|
}
|
|
|
|
net.clear();
|
|
if (!empty_net(net) || state.destroyed != 1 || state.pipeline_destroyed != 1 || state.pipeline_destroyed_before_param != 0)
|
|
{
|
|
fprintf(stderr, "test_net pipeline clear failed binary=%d destroyed=%d pipelines=%d pipelines_before_param=%d\n", binary, state.destroyed, state.pipeline_destroyed, state.pipeline_destroyed_before_param);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_bounded_text()
|
|
{
|
|
const unsigned char text[] = "7767517\n1 1\nInput input 0 1 out\n";
|
|
const unsigned char* mem = text;
|
|
ncnn::DataReaderFromMemory dr(mem, sizeof(text) - 1);
|
|
ncnn::Net net;
|
|
if (net.load_param(dr) != 0)
|
|
{
|
|
fprintf(stderr, "test_net bounded text failed\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined _WIN32 || __ANDROID__ || defined __OHOS__ || defined __linux__ || __APPLE__
|
|
class TestModelLayer : public ncnn::Layer
|
|
{
|
|
public:
|
|
virtual int load_model(const ncnn::ModelBin& mb)
|
|
{
|
|
weights = mb.load(3, 1);
|
|
return weights.empty() ? -100 : 0;
|
|
}
|
|
|
|
virtual int forward(const std::vector<ncnn::Mat>&, std::vector<ncnn::Mat>& top_blobs, const ncnn::Option& opt) const
|
|
{
|
|
top_blobs[0] = weights.clone(opt.blob_allocator);
|
|
return top_blobs[0].empty() ? -100 : 0;
|
|
}
|
|
|
|
private:
|
|
ncnn::Mat weights;
|
|
};
|
|
|
|
static ncnn::Layer* create_model_layer(void*)
|
|
{
|
|
return new TestModelLayer;
|
|
}
|
|
|
|
static int check_model_file(const char* path, size_t size, bool mapped)
|
|
{
|
|
const unsigned char model_data[] = {
|
|
0x00, 0x00, 0x80, 0x3f,
|
|
0x00, 0x00, 0x20, 0xc0,
|
|
0x00, 0x00, 0x00, 0x3f
|
|
};
|
|
const float weights[] = {1.f, -2.5f, 0.5f};
|
|
FILE* fp = fopen(path, "wb");
|
|
if (!fp)
|
|
{
|
|
fprintf(stderr, "test_net model fopen failed\n");
|
|
return -1;
|
|
}
|
|
const size_t nwrite = fwrite(model_data, 1, size, fp);
|
|
const int close_ret = fclose(fp);
|
|
if (nwrite != size || close_ret != 0)
|
|
{
|
|
fprintf(stderr, "test_net model fwrite failed\n");
|
|
return -1;
|
|
}
|
|
|
|
// mapped loading falls back to stdio when file mapping is unavailable
|
|
if (mapped && size != 0)
|
|
{
|
|
ncnn::MappedFile file;
|
|
if (file.open(path) != 0)
|
|
{
|
|
fprintf(stderr, "test_net model mapping unavailable size=%zu errno=%d, testing stdio fallback\n", size, errno);
|
|
}
|
|
}
|
|
|
|
ncnn::Net net;
|
|
net.opt.use_mapped_model_loading = mapped;
|
|
net.register_custom_layer("TestModel", create_model_layer);
|
|
if (net.load_param_mem("7767517\n1 1\nTestModel weights 0 1 out\n") != 0)
|
|
{
|
|
fprintf(stderr, "test_net model load_param failed\n");
|
|
return -1;
|
|
}
|
|
|
|
// a bounded short read consumes the entire file, so the load status must be checked
|
|
const int ret = net.load_model(path);
|
|
if ((ret != 0) != (size < sizeof(weights)))
|
|
{
|
|
fprintf(stderr, "test_net model load failed mapped=%d size=%zu ret=%d\n", mapped, size, ret);
|
|
return -1;
|
|
}
|
|
|
|
#if _WIN32
|
|
wchar_t wide_path[128];
|
|
size_t i = 0;
|
|
for (; path[i]; i++)
|
|
wide_path[i] = (unsigned char)path[i];
|
|
wide_path[i] = 0;
|
|
ncnn::Net net2;
|
|
net2.opt.use_mapped_model_loading = mapped;
|
|
net2.register_custom_layer("TestModel", create_model_layer);
|
|
if (net2.load_param_mem("7767517\n1 1\nTestModel weights 0 1 out\n") != 0
|
|
|| (net2.load_model(wide_path) != 0) != (size < sizeof(weights)))
|
|
{
|
|
fprintf(stderr, "test_net wide model load failed mapped=%d size=%zu\n", mapped, size);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
if (ret == 0)
|
|
{
|
|
ncnn::Mat output;
|
|
ncnn::Extractor ex = net.create_extractor();
|
|
if (ex.extract("out", output) != 0 || output.w != 3 || memcmp(output.data, weights, sizeof(weights)))
|
|
{
|
|
fprintf(stderr, "test_net model output failed mapped=%d\n", mapped);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_model_file()
|
|
{
|
|
char path[128];
|
|
#if _WIN32
|
|
snprintf(path, sizeof(path), "test_net_model.%u.bin", (unsigned int)_getpid());
|
|
#else
|
|
snprintf(path, sizeof(path), "test_net_model.%u.bin", (unsigned int)getpid());
|
|
#endif
|
|
for (int mapped = 0; mapped < 2; mapped++)
|
|
{
|
|
for (size_t size = 0; size <= 3 * sizeof(float); size++)
|
|
{
|
|
const int ret = check_model_file(path, size, mapped != 0);
|
|
remove(path);
|
|
if (ret != 0)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif // defined _WIN32 || __ANDROID__ || defined __OHOS__ || defined __linux__ || __APPLE__
|
|
|
|
#if NCNN_VULKAN
|
|
class OptionalInputLayer : public ncnn::Layer
|
|
{
|
|
public:
|
|
OptionalInputLayer(bool _support_vulkan)
|
|
{
|
|
one_blob_only = false;
|
|
support_vulkan = _support_vulkan;
|
|
}
|
|
|
|
virtual int forward(const std::vector<ncnn::Mat>& bottom_blobs, std::vector<ncnn::Mat>& top_blobs, const ncnn::Option&) const
|
|
{
|
|
top_blobs[0] = bottom_blobs.size() == 2 && !bottom_blobs[1].empty() ? bottom_blobs[1] : bottom_blobs[0];
|
|
return 0;
|
|
}
|
|
|
|
virtual int forward(const std::vector<ncnn::VkMat>& bottom_blobs, std::vector<ncnn::VkMat>& top_blobs, ncnn::VkCompute& cmd, const ncnn::Option& opt) const
|
|
{
|
|
const ncnn::VkMat& a = bottom_blobs.size() == 2 && !bottom_blobs[1].empty() ? bottom_blobs[1] : bottom_blobs[0];
|
|
cmd.record_clone(a, top_blobs[0], opt);
|
|
return top_blobs[0].empty() ? -100 : 0;
|
|
}
|
|
};
|
|
|
|
static ncnn::Layer* create_optional_input_layer(void*)
|
|
{
|
|
return new OptionalInputLayer(false);
|
|
}
|
|
|
|
static ncnn::Layer* create_optional_input_layer_vulkan(void*)
|
|
{
|
|
return new OptionalInputLayer(true);
|
|
}
|
|
|
|
static int test_vulkan_optional_input(int vulkan, int lightmode, int empty_input)
|
|
{
|
|
ncnn::Net net;
|
|
net.opt.use_vulkan_compute = true;
|
|
net.opt.lightmode = lightmode;
|
|
net.opt.num_threads = 1;
|
|
net.register_custom_layer("OptionalInput", create_optional_input_layer);
|
|
net.register_custom_layer("OptionalInputVulkan", create_optional_input_layer_vulkan);
|
|
|
|
const char* param_cpu = "7767517\n"
|
|
"4 4\n"
|
|
"Input in 0 1 in\n"
|
|
"Input optional 0 1 optional\n"
|
|
"OptionalInputVulkan pre 1 1 in data\n"
|
|
"OptionalInput target 2 1 data optional out\n";
|
|
const char* param_vulkan = "7767517\n"
|
|
"4 4\n"
|
|
"Input in 0 1 in\n"
|
|
"Input optional 0 1 optional\n"
|
|
"OptionalInput pre 1 1 in data\n"
|
|
"OptionalInputVulkan target 2 1 data optional out\n";
|
|
BoundedNetReader dr(0, 0);
|
|
if (net.load_param_mem(vulkan ? param_vulkan : param_cpu) != 0 || net.load_model(dr) != 0)
|
|
{
|
|
fprintf(stderr, "test_net vulkan optional input load failed vulkan=%d lightmode=%d\n", vulkan, lightmode);
|
|
return -1;
|
|
}
|
|
|
|
ncnn::Mat in(4);
|
|
ncnn::Mat optional(4);
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
in[i] = i + 1.f;
|
|
optional[i] = i + 11.f;
|
|
}
|
|
|
|
ncnn::Extractor ex = net.create_extractor();
|
|
ex.input("in", in);
|
|
if (!empty_input)
|
|
ex.input("optional", optional);
|
|
|
|
ncnn::Mat out;
|
|
int ret = ex.extract("out", out);
|
|
const ncnn::Mat& expected = empty_input ? in : optional;
|
|
if (ret != 0 || CompareMat(expected, out, 0.f) != 0)
|
|
{
|
|
fprintf(stderr, "test_net vulkan optional input failed vulkan=%d lightmode=%d empty_input=%d ret=%d\n", vulkan, lightmode, empty_input, ret);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_vulkan_optional_input()
|
|
{
|
|
if (ncnn::get_gpu_count() == 0)
|
|
return 0;
|
|
|
|
return test_vulkan_optional_input(0, 0, 1)
|
|
|| test_vulkan_optional_input(0, 0, 0)
|
|
|| test_vulkan_optional_input(0, 1, 1)
|
|
|| test_vulkan_optional_input(0, 1, 0)
|
|
|| test_vulkan_optional_input(1, 0, 1)
|
|
|| test_vulkan_optional_input(1, 0, 0)
|
|
|| test_vulkan_optional_input(1, 1, 1)
|
|
|| test_vulkan_optional_input(1, 1, 0);
|
|
}
|
|
#endif // NCNN_VULKAN
|
|
|
|
#if NCNN_VALIDATION
|
|
static int test_text_errors()
|
|
{
|
|
const char* cases[] = {
|
|
"7767517\n1 1\nTest t 3 0 a a b\n",
|
|
"", "7767517", "7767517\n0 1\n", "7767517\n1 -1\n",
|
|
"7767517\n2147483647 1\n", "7767517\n1 2147483647\n",
|
|
"7767517\n1000001 1\n", "7767517\n1 1000001\n",
|
|
"7767517\n1 1\nTest t 1000001 0\n", "7767517\n1 1\nTest t 0 1000001\n",
|
|
"7767517\n4294967297 1\nTest t 0 1 out\n",
|
|
"7767517\n1 1\nTest t -1 1 out\n", "7767517\n1 1\nTest t 0 -1\n",
|
|
"7767517\n1 1\nTest t 2147483647 1\n", "7767517\n1 1\nTest t 0 2147483647\n",
|
|
"7767517\n1 1\nTest t 0 4294967297 out\n",
|
|
"7767517\n1x 1\nTest t 0 1 out\n",
|
|
"7767517\n1 1\nTest t 0 1x out\n",
|
|
"7767517\n1 1\nTest t 1 1 in out\n",
|
|
"7767517\n1 1\nTest t 2 0 in in2\n",
|
|
"7767517\n1 1\nTest t 0 2 out out2\n",
|
|
"7767517\n1 1\nTest t 1 0", "7767517\n1 1\nTest t 0 1",
|
|
"7767517\n2 1\nTest t 0 1 out\n",
|
|
"7767517\n1 1\nTest t 0 1 out 0=1,,2\n",
|
|
"7767517\n1 1\nTest t 0 1 out 31=1.0\n",
|
|
"7767517\n1 1\nTest t 0 1 out 31=1,2\n",
|
|
// missing bottom name checks builtin override cleanup after a truncated connection
|
|
"7767517\n1 1\nInput t 1 0",
|
|
"7767517\n1 1\nInput t 0 1 out 0=1,,2\n"
|
|
};
|
|
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
|
|
{
|
|
if (check_text(cases[i], -1))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_one_blob_only()
|
|
{
|
|
const int counts[][2] = {{0, 1}, {1, 0}, {1, 2}, {2, 1}};
|
|
for (int binary = 0; binary < 2; binary++)
|
|
{
|
|
for (size_t i = 0; i < sizeof(counts) / sizeof(counts[0]); i++)
|
|
{
|
|
const int bottom_count = counts[i][0];
|
|
const int top_count = counts[i][1];
|
|
LayerState state;
|
|
state.one_blob_only = true;
|
|
ncnn::Net net;
|
|
net.register_custom_layer("Test", create_test_layer, destroy_test_layer, &state);
|
|
|
|
std::vector<unsigned char> data = binary_header(1, bottom_count + top_count);
|
|
append_layer_header(data, ncnn::LayerType::CustomBit, bottom_count, top_count);
|
|
for (int j = 0; j < bottom_count + top_count; j++) append_int(data, j);
|
|
append_int(data, -233);
|
|
const char* text[] = {
|
|
"7767517\n1 1\nTest t 0 1 out\n",
|
|
"7767517\n1 1\nTest t 1 0 in\n",
|
|
"7767517\n1 3\nTest t 1 2 in out out2\n",
|
|
"7767517\n1 3\nTest t 2 1 in in2 out\n"
|
|
};
|
|
int ret = binary ? load_binary(net, &data[0], data.size()) : net.load_param_mem(text[i]);
|
|
if (ret != -1 || !empty_net(net) || state.created != 1 || state.destroyed != 1 || state.pipeline_destroyed != 0)
|
|
{
|
|
fprintf(stderr, "test_net one_blob_only failed binary=%d bottoms=%d tops=%d ret=%d created=%d destroyed=%d pipelines=%d\n", binary, bottom_count, top_count, ret, state.created, state.destroyed, state.pipeline_destroyed);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_shape_hint_errors()
|
|
{
|
|
struct Case
|
|
{
|
|
const char* text;
|
|
int count;
|
|
int values[10];
|
|
int tops;
|
|
};
|
|
const Case cases[] = {
|
|
{"1,4", 1, {4}, 1},
|
|
{"3,3,7,6", 3, {3, 7, 6}, 1},
|
|
{"4,4,7,6,5", 4, {4, 7, 6, 5}, 1},
|
|
{"5,3,7,6,1,5", 5, {3, 7, 6, 1, 5}, 2},
|
|
{"4,5,7,6,5", 4, {5, 7, 6, 5}, 1},
|
|
{"4,-1,7,6,5", 4, {-1, 7, 6, 5}, 1},
|
|
{"4,3,-1,6,5", 4, {3, -1, 6, 5}, 1},
|
|
{"5,4,2147483647,2147483647,2147483647,1", 5, {4, INT_MAX, INT_MAX, INT_MAX, 1}, 1},
|
|
{"4,3,2147483647,2147483647,2147483647", 4, {3, INT_MAX, INT_MAX, INT_MAX}, 1}
|
|
};
|
|
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
|
|
{
|
|
const Case& c = cases[i];
|
|
char text[512];
|
|
const char* top_names[] = {"", " out", " out out2"};
|
|
snprintf(text, sizeof(text), "7767517\n1 2\nTest t 0 %d%s -23330=%s\n", c.tops, top_names[c.tops], c.text);
|
|
if (check_text(text, -1))
|
|
return -1;
|
|
|
|
std::vector<unsigned char> data = binary_header(1, 2);
|
|
append_layer_header(data, ncnn::LayerType::CustomBit, 0, c.tops);
|
|
for (int j = 0; j < c.tops; j++) append_int(data, j);
|
|
append_int(data, -23330);
|
|
append_int(data, c.count);
|
|
for (int j = 0; j < c.count; j++) append_int(data, c.values[j]);
|
|
append_int(data, -233);
|
|
if (check_binary(c.text, data, data.size(), -1))
|
|
return -1;
|
|
}
|
|
if (check_text("7767517\n1 1\nTest t 0 1 out 30=4\n", -1)
|
|
|| check_text("7767517\n1 1\nTest t 0 1 out 30=3.0,7.0,6.0,5.0\n", -1))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_custom_index_limit()
|
|
{
|
|
ncnn::Net net;
|
|
const int indexes[] = {-1, 1000000 | ncnn::LayerType::CustomBit, INT_MAX};
|
|
for (size_t i = 0; i < sizeof(indexes) / sizeof(indexes[0]); i++)
|
|
{
|
|
int ret = net.register_custom_layer(indexes[i], create_null_layer);
|
|
if (ret != -1)
|
|
{
|
|
fprintf(stderr, "test_net custom layer index limit failed index=%d ret=%d\n", indexes[i], ret);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_binary_param_types()
|
|
{
|
|
const int ids[] = {30, -23331};
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, ncnn::LayerType::CustomBit, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, ids[i]);
|
|
append_int(data, 1);
|
|
if (i == 1) append_int(data, 1);
|
|
append_int(data, -233);
|
|
if (check_binary("parameter type", data, data.size(), -1))
|
|
{
|
|
fprintf(stderr, "test_net binary parameter type failed id=%d\n", ids[i]);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_binary_errors()
|
|
{
|
|
const int custom = ncnn::LayerType::CustomBit;
|
|
for (int builtin = 0; builtin < 2; builtin++)
|
|
{
|
|
const int type = builtin ? ncnn::LayerType::Input : custom;
|
|
const char* layer_name = builtin ? "overwritten layer" : "custom layer";
|
|
const int bad[] = {-1, INT_MIN, 2, INT_MAX};
|
|
for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++)
|
|
{
|
|
for (int bottom = 0; bottom < 2; bottom++)
|
|
{
|
|
std::vector<unsigned char> data = binary_header(1, 2);
|
|
append_layer_header(data, type, bottom, 1 - bottom);
|
|
append_int(data, bad[i]);
|
|
append_int(data, -233);
|
|
if (check_binary(layer_name, data, data.size(), -1))
|
|
{
|
|
fprintf(stderr, "test_net blob index failed bottom=%d index=%d\n", bottom, bad[i]);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const int bad_counts[] = {-1, INT_MIN, 1000001, INT_MAX};
|
|
for (size_t i = 0; i < sizeof(bad_counts) / sizeof(bad_counts[0]); i++)
|
|
{
|
|
for (int field = 0; field < 4; field++)
|
|
{
|
|
int counts[] = {1, 1, 0, 1};
|
|
counts[field] = bad_counts[i];
|
|
std::vector<unsigned char> data = binary_header(counts[0], counts[1]);
|
|
append_layer_header(data, custom, counts[2], counts[3]);
|
|
if (check_binary("header count", data, data.size(), -1))
|
|
{
|
|
fprintf(stderr, "test_net header count failed field=%d value=%d\n", field, bad_counts[i]);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
std::vector<unsigned char> data = binary_header(1, 1);
|
|
append_layer_header(data, -1, 0, 1);
|
|
append_int(data, 0);
|
|
append_int(data, -233);
|
|
if (check_binary("negative layer type", data, data.size(), -1))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_binary_cycles()
|
|
{
|
|
const int custom = ncnn::LayerType::CustomBit;
|
|
struct Case
|
|
{
|
|
const char* name;
|
|
int layers;
|
|
int blobs;
|
|
int values[24];
|
|
int count;
|
|
int expected_ret;
|
|
};
|
|
const Case cases[] = {
|
|
{"self cycle", 1, 1, {custom, 1, 1, 0, 0, -233}, 6, -1},
|
|
{"two layer cycle", 2, 2, {custom, 1, 1, 1, 0, -233, custom, 1, 1, 0, 1, -233}, 12, -1},
|
|
{"disconnected cycle", 3, 3, {custom, 0, 1, 0, -233, custom, 1, 1, 2, 1, -233, custom, 1, 1, 1, 2, -233}, 17, -1},
|
|
{"reassigned producer cycle", 3, 2, {custom, 0, 1, 0, -233, custom, 1, 1, 0, 1, -233, custom, 1, 1, 1, 0, -233}, 17, -1},
|
|
};
|
|
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
|
|
{
|
|
const Case& c = cases[i];
|
|
std::vector<unsigned char> data;
|
|
append_int(data, 7767517);
|
|
append_int(data, c.layers);
|
|
append_int(data, c.blobs);
|
|
for (int j = 0; j < c.count; j++)
|
|
append_int(data, c.values[j]);
|
|
|
|
if (check_binary(c.name, data, data.size(), c.expected_ret))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_name_length_invalid()
|
|
{
|
|
char name[257];
|
|
memset(name, 'a', 256);
|
|
name[256] = 0;
|
|
char text[1024];
|
|
snprintf(text, sizeof(text), "7767517\n1 1\nTest %s 0 1 out\n", name);
|
|
if (check_text(text, -1))
|
|
return -1;
|
|
snprintf(text, sizeof(text), "7767517\n1 1\nTest t 0 1 %s\n", name);
|
|
if (check_text(text, -1))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
#endif // NCNN_VALIDATION
|
|
|
|
int main()
|
|
{
|
|
return 0
|
|
|| test_binary_truncated()
|
|
|| test_binary_dependencies()
|
|
|| test_binary_deep_dependencies(0)
|
|
|| test_shape_hints()
|
|
|| test_layer_load_param_error()
|
|
|| test_reload()
|
|
|| test_name_length()
|
|
|| test_integer_length(30)
|
|
|| test_integer_length(31)
|
|
|| test_repeated_blob_references()
|
|
|| test_builtin_without_override()
|
|
|| test_null_creators()
|
|
|| test_shape_layout()
|
|
#if NCNN_BF16
|
|
|| test_storage_packing(4)
|
|
|| test_storage_packing(2)
|
|
#endif
|
|
|| test_recreate_layer()
|
|
|| test_recreate_layer_null_creator()
|
|
|| test_external_input()
|
|
|| test_binary_layer_types()
|
|
|| test_magic_mismatch()
|
|
|| test_feature_mask()
|
|
|| test_pipeline_lifecycle()
|
|
|| test_bounded_text()
|
|
#if defined _WIN32 || __ANDROID__ || defined __OHOS__ || defined __linux__ || __APPLE__
|
|
|| test_model_file()
|
|
#endif
|
|
#if NCNN_VULKAN
|
|
|| test_vulkan_optional_input()
|
|
#endif // NCNN_VULKAN
|
|
#if NCNN_VALIDATION
|
|
|| test_text_errors()
|
|
|| test_binary_errors()
|
|
|| test_binary_cycles()
|
|
|| test_binary_deep_dependencies(1)
|
|
|| test_name_length_invalid()
|
|
|| test_integer_length(32)
|
|
|| test_shape_hint_errors()
|
|
|| test_one_blob_only()
|
|
|| test_custom_index_limit()
|
|
|| test_binary_param_types()
|
|
#endif // NCNN_VALIDATION
|
|
;
|
|
}
|