// Copyright 2026 Tencent // SPDX-License-Identifier: BSD-3-Clause #include #include #include #include "datareader.h" #include "layer_type.h" #include "modelbin.h" #include "net.h" #include "paramdict.h" #include "testutil.h" #include #if defined _WIN32 #include #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& 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 binary_header(int layers, int blobs) { std::vector data; append_int(data, 7767517); append_int(data, layers); append_int(data, blobs); return data; } static void append_layer_header(std::vector& 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& 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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&, std::vector& 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& bottom_blobs, std::vector& 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& bottom_blobs, std::vector& 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 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 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 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 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 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 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 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 ; }