// Copyright 2022 Google LLC // // This source code is licensed under the BSD-style license found in the // LICENSE file in the root directory of this source tree. #include #include #include #include #include #include #include #include #include namespace { void DefineGraphWithoutInternalTensors(xnn_subgraph_t* subgraph, std::array dims) { xnn_create_subgraph(/*external_value_ids=*/0, /*flags=*/0, subgraph); uint32_t input_id = XNN_INVALID_VALUE_ID; xnn_define_tensor_value( *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID, XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id); ASSERT_NE(input_id, XNN_INVALID_VALUE_ID); uint32_t output_id = XNN_INVALID_VALUE_ID; xnn_define_tensor_value( *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID, XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id); ASSERT_NE(output_id, XNN_INVALID_VALUE_ID); ASSERT_EQ(xnn_status_success, xnn_define_abs(*subgraph, input_id, output_id, /*flags=*/0)); } // Helper function to create a subgraph with 1 input, 1 output, and 1 intermediate tensor. // input -> (abs) -> intermediate -> (hard swish) -> output // The size of the tensors are all the same, specified by `dims`. void DefineGraph(xnn_subgraph_t* subgraph, std::array dims) { xnn_create_subgraph(/*external_value_ids=*/0, /*flags=*/0, subgraph); uint32_t input_id = XNN_INVALID_VALUE_ID; xnn_define_tensor_value( *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID, XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id); ASSERT_NE(input_id, XNN_INVALID_VALUE_ID); uint32_t intermediate_id = XNN_INVALID_VALUE_ID; xnn_define_tensor_value( *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID, /*flags=*/0, &intermediate_id); ASSERT_NE(intermediate_id, XNN_INVALID_VALUE_ID); uint32_t output_id = XNN_INVALID_VALUE_ID; xnn_define_tensor_value( *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID, XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id); ASSERT_NE(output_id, XNN_INVALID_VALUE_ID); ASSERT_EQ(xnn_status_success, xnn_define_abs(*subgraph, input_id, intermediate_id, /*flags=*/0)); ASSERT_EQ(xnn_status_success, xnn_define_hardswish(*subgraph, intermediate_id, output_id, /*flags=*/0)); } void DefineGraphWithStaticData(xnn_subgraph_t* subgraph, std::array dims, const std::vector* static_value) { xnn_create_subgraph(/*external_value_ids=*/0, /*flags=*/0, subgraph); uint32_t input_id = XNN_INVALID_VALUE_ID; xnn_define_tensor_value( *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID, XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id); ASSERT_NE(input_id, XNN_INVALID_VALUE_ID); uint32_t static_value_id = XNN_INVALID_VALUE_ID; xnn_define_tensor_value( *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), static_value->data(), XNN_INVALID_VALUE_ID, /*flags=*/0, &static_value_id); ASSERT_NE(static_value_id, XNN_INVALID_VALUE_ID); uint32_t output_id = XNN_INVALID_VALUE_ID; xnn_define_tensor_value( *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID, XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id); ASSERT_NE(output_id, XNN_INVALID_VALUE_ID); ASSERT_EQ(xnn_status_success, xnn_define_add2(*subgraph, -std::numeric_limits::infinity(), std::numeric_limits::infinity(), input_id, static_value_id, output_id, /*flags=*/0)); } testing::AssertionResult BlobInWorkspace(xnn_blob* blob, xnn_workspace_t workspace) { if ((blob->data >= workspace->data) && ((uintptr_t) blob->data + blob->size) <= ((uintptr_t) workspace->data + workspace->size)) { return testing::AssertionSuccess(); } else { return testing::AssertionFailure() << "blob at " << blob->data << " of size " << blob->size << "is outside of workspace at " << workspace->data << " of size " << workspace->size; } } testing::AssertionResult Contains(std::vector workspace_users, xnn_runtime_t runtime) { if (std::find(workspace_users.begin(), workspace_users.end(), runtime) != workspace_users.end()) { return testing::AssertionSuccess(); } else { return testing::AssertionFailure() << "runtime " << runtime << " not found in list of workspace users"; } } std::vector workspace_user_to_list(xnn_workspace_t workspace) { std::vector users; for (xnn_runtime_t rt = workspace->first_user; rt != NULL; rt = rt->next_workspace_user) { users.push_back(rt); } return users; } } // namespace TEST(WORKSPACE, static_data_not_moved_does_not_segv) { std::array dims = {2, 20, 20, 3}; size_t num_elements = dims[0] * dims[1] * dims[2] * dims[3]; xnn_initialize(/*allocator=*/nullptr); xnn_workspace_t workspace = nullptr; xnn_create_workspace(&workspace); std::unique_ptr auto_workspace(workspace, xnn_release_workspace); // Create a graph that with static data. xnn_subgraph_t subgraph1 = nullptr; std::vector static_data = std::vector(num_elements, 1.0f); DefineGraphWithStaticData(&subgraph1, dims, &static_data); std::unique_ptr auto_subgraph1(subgraph1, xnn_delete_subgraph); xnn_runtime_t runtime1 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1)); std::unique_ptr auto_runtime1(runtime1, xnn_delete_runtime); // The workspace remains at size 0, without any memory allocated, since we don't have any internal tensors. size_t old_workspace_size = workspace->size; ASSERT_EQ(old_workspace_size, 0); void* old_runtime_workspace = runtime1->workspace->data; ASSERT_EQ(old_runtime_workspace, nullptr); // Then create a graph that has internal tensors, we will need to resize the workspace. xnn_subgraph_t subgraph2 = nullptr; DefineGraph(&subgraph2, dims); std::unique_ptr auto_subgraph2(subgraph2, xnn_delete_subgraph); xnn_runtime_t runtime2 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2)); std::unique_ptr auto_runtime2(runtime2, xnn_delete_runtime); // Check that the workspace grew. ASSERT_GE(workspace->size, num_elements * sizeof(float)); ASSERT_NE(runtime2->workspace->data, nullptr); // Try to access all the blobs and ensure that we don't segfault. for (size_t i = 0; i < runtime1->num_blobs; i++) { xnn_blob* blob = &runtime1->blobs[i]; if (blob->allocation_type == xnn_allocation_type_external) { continue; } ASSERT_GT(blob->size, 0); char access = *((char *)blob->data); (void) access; } for (size_t i = 0; i < runtime2->num_blobs; i++) { xnn_blob* blob = &runtime2->blobs[i]; if (blob->allocation_type == xnn_allocation_type_external) { continue; } ASSERT_GT(blob->size, 0); char access = *((char *)blob->data); (void) access; } } TEST(WORKSPACE, workspace_no_growth) { xnn_initialize(/*allocator=*/nullptr); xnn_workspace_t workspace = nullptr; xnn_create_workspace(&workspace); std::unique_ptr auto_workspace(workspace, xnn_release_workspace); std::array dims = {2, 20, 20, 3}; xnn_subgraph_t subgraph1 = nullptr; DefineGraph(&subgraph1, dims); std::unique_ptr auto_subgraph1(subgraph1, xnn_delete_subgraph); xnn_runtime_t runtime1 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1)); std::unique_ptr auto_runtime1(runtime1, xnn_delete_runtime); size_t old_workspace_size = workspace->size; ASSERT_GE(old_workspace_size, 0); void* old_runtime_workspace = runtime1->workspace->data; ASSERT_NE(old_runtime_workspace, nullptr); // Create the same graph again with a different runtime that shares the workspace. xnn_subgraph_t subgraph2 = nullptr; DefineGraph(&subgraph2, dims); std::unique_ptr auto_subgraph2(subgraph2, xnn_delete_subgraph); xnn_runtime_t runtime2 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2)); std::unique_ptr auto_runtime2(runtime2, xnn_delete_runtime); // Check that the workspace did not grow. ASSERT_EQ(workspace->size, old_workspace_size); // Check that runtime 2 uses the same workspace. ASSERT_EQ(runtime2->workspace->data, old_runtime_workspace); ASSERT_EQ(runtime1->num_blobs, runtime2->num_blobs); for (size_t i = 0; i < runtime1->num_blobs; i++) { xnn_blob* blob1 = &runtime1->blobs[i]; if (blob1->allocation_type != xnn_allocation_type_workspace) { continue; } ASSERT_TRUE(BlobInWorkspace(blob1, runtime1->workspace)); xnn_blob* blob2 = &runtime2->blobs[i]; ASSERT_TRUE(BlobInWorkspace(blob2, runtime2->workspace)); } std::vector workspace_users = workspace_user_to_list(workspace); ASSERT_EQ(workspace_users.size(), 2); ASSERT_TRUE(Contains(workspace_users, runtime1)); ASSERT_TRUE(Contains(workspace_users, runtime2)); ASSERT_EQ(workspace->ref_count, 3); } TEST(WORKSPACE, workspace_grow) { xnn_initialize(/*allocator=*/nullptr); xnn_workspace_t workspace = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_workspace(&workspace)); std::unique_ptr auto_workspace(workspace, xnn_release_workspace); std::array dims1 = {2, 20, 20, 3}; xnn_subgraph_t subgraph1 = nullptr; DefineGraph(&subgraph1, dims1); std::unique_ptr auto_subgraph1(subgraph1, xnn_delete_subgraph); xnn_runtime_t runtime1 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1)); std::unique_ptr auto_runtime1(runtime1, xnn_delete_runtime); size_t old_workspace_size = workspace->size; ASSERT_GE(old_workspace_size, 0); void* old_runtime_workspace = runtime1->workspace->data; ASSERT_NE(old_runtime_workspace, nullptr); std::array dims2 = dims1; // Create the same graph but with larger tensors, this will require a larger workspace. std::transform(dims2.begin(), dims2.end(), dims2.begin(), [](size_t i) { return i * 2; }); xnn_subgraph_t subgraph2 = nullptr; DefineGraph(&subgraph2, dims2); std::unique_ptr auto_subgraph2(subgraph2, xnn_delete_subgraph); xnn_runtime_t runtime2 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2)); std::unique_ptr auto_runtime2(runtime2, xnn_delete_runtime); // Check that the workspace grew. ASSERT_GE(workspace->size, old_workspace_size); // Check that runtime 2 uses the same workspace. ASSERT_NE(runtime2->workspace->data, old_runtime_workspace); // Check that runtime1's workspace has been updated as well. ASSERT_EQ(runtime1->workspace->data, runtime2->workspace->data); ASSERT_EQ(runtime1->workspace->size, runtime2->workspace->size); // Check that both runtime's blob pointers are within range. for (size_t i = 0; i < runtime1->num_blobs; i++) { xnn_blob* blob = &runtime1->blobs[i]; if (blob->allocation_type != xnn_allocation_type_workspace) { continue; } ASSERT_TRUE(BlobInWorkspace(blob, runtime1->workspace)); } for (size_t i = 0; i < runtime2->num_blobs; i++) { xnn_blob* blob = &runtime2->blobs[i]; if (blob->allocation_type != xnn_allocation_type_workspace) { continue; } ASSERT_TRUE(BlobInWorkspace(blob, runtime2->workspace)); } std::vector workspace_users = workspace_user_to_list(workspace); ASSERT_EQ(workspace_users.size(), 2); ASSERT_TRUE(Contains(workspace_users, runtime1)); ASSERT_TRUE(Contains(workspace_users, runtime2)); ASSERT_EQ(workspace->ref_count, 3); } TEST(WORKSPACE, workspace_runtime_delete_head_runtime_first) { xnn_initialize(/*allocator=*/nullptr); xnn_workspace_t workspace = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_workspace(&workspace)); std::unique_ptr auto_workspace(workspace, xnn_release_workspace); const std::array dims = {2, 20, 20, 3}; xnn_subgraph_t subgraph1 = nullptr; DefineGraph(&subgraph1, dims); std::unique_ptr auto_subgraph1(subgraph1, xnn_delete_subgraph); xnn_runtime_t runtime1 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1)); std::unique_ptr auto_runtime1(runtime1, xnn_delete_runtime); xnn_subgraph_t subgraph2 = nullptr; DefineGraph(&subgraph2, dims); std::unique_ptr auto_subgraph2(subgraph2, xnn_delete_subgraph); xnn_runtime_t runtime2 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2)); std::unique_ptr auto_runtime2(runtime2, xnn_delete_runtime); ASSERT_EQ(workspace->first_user, runtime2); ASSERT_EQ(runtime2->next_workspace_user, runtime1); ASSERT_EQ(runtime1->next_workspace_user, nullptr); ASSERT_EQ(workspace->ref_count, 3); xnn_delete_runtime(auto_runtime2.release()); ASSERT_EQ(workspace->first_user, runtime1); ASSERT_EQ(runtime1->next_workspace_user, nullptr); ASSERT_EQ(workspace->ref_count, 2); xnn_delete_runtime(auto_runtime1.release()); ASSERT_EQ(workspace->first_user, nullptr); ASSERT_EQ(workspace->ref_count, 1); } TEST(WORKSPACE, workspace_runtime_delete_tail_runtime_first) { xnn_initialize(/*allocator=*/nullptr); xnn_workspace_t workspace = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_workspace(&workspace)); std::unique_ptr auto_workspace(workspace, xnn_release_workspace); std::array dims = {2, 20, 20, 3}; xnn_subgraph_t subgraph1 = nullptr; DefineGraph(&subgraph1, dims); std::unique_ptr auto_subgraph1(subgraph1, xnn_delete_subgraph); xnn_runtime_t runtime1 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1)); std::unique_ptr auto_runtime1(runtime1, xnn_delete_runtime); xnn_subgraph_t subgraph2 = nullptr; DefineGraph(&subgraph2, dims); std::unique_ptr auto_subgraph2(subgraph2, xnn_delete_subgraph); xnn_runtime_t runtime2 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2)); std::unique_ptr auto_runtime2(runtime2, xnn_delete_runtime); ASSERT_EQ(workspace->first_user, runtime2); ASSERT_EQ(runtime2->next_workspace_user, runtime1); ASSERT_EQ(runtime1->next_workspace_user, nullptr); ASSERT_EQ(workspace->ref_count, 3); xnn_delete_runtime(auto_runtime1.release()); ASSERT_EQ(workspace->first_user, runtime2); ASSERT_EQ(runtime2->next_workspace_user, nullptr); ASSERT_EQ(workspace->ref_count, 2); xnn_delete_runtime(auto_runtime2.release()); ASSERT_EQ(workspace->first_user, nullptr); ASSERT_EQ(workspace->ref_count, 1); } TEST(WORKSPACE, workspace_runtime_delete_middle_runtime_first) { xnn_initialize(/*allocator=*/nullptr); xnn_workspace_t workspace = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_workspace(&workspace)); std::unique_ptr auto_workspace(workspace, xnn_release_workspace); std::array dims = {2, 20, 20, 3}; xnn_subgraph_t subgraph1 = nullptr; DefineGraph(&subgraph1, dims); std::unique_ptr auto_subgraph1(subgraph1, xnn_delete_subgraph); xnn_runtime_t runtime1 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1)); std::unique_ptr auto_runtime1(runtime1, xnn_delete_runtime); xnn_subgraph_t subgraph2 = nullptr; DefineGraph(&subgraph2, dims); std::unique_ptr auto_subgraph2(subgraph2, xnn_delete_subgraph); xnn_runtime_t runtime2 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2)); std::unique_ptr auto_runtime2(runtime2, xnn_delete_runtime); xnn_subgraph_t subgraph3 = nullptr; DefineGraph(&subgraph3, dims); std::unique_ptr auto_subgraph3(subgraph3, xnn_delete_subgraph); xnn_runtime_t runtime3 = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph3, nullptr, workspace, nullptr, 0, &runtime3)); std::unique_ptr auto_runtime3(runtime3, xnn_delete_runtime); ASSERT_EQ(workspace->first_user, runtime3); ASSERT_EQ(runtime3->next_workspace_user, runtime2); ASSERT_EQ(runtime2->next_workspace_user, runtime1); ASSERT_EQ(runtime1->next_workspace_user, nullptr); ASSERT_EQ(workspace->ref_count, 4); xnn_delete_runtime(auto_runtime2.release()); ASSERT_EQ(workspace->first_user, runtime3); ASSERT_EQ(runtime3->next_workspace_user, runtime1); ASSERT_EQ(runtime1->next_workspace_user, nullptr); ASSERT_EQ(workspace->ref_count, 3); xnn_delete_runtime(auto_runtime3.release()); ASSERT_EQ(workspace->first_user, runtime1); ASSERT_EQ(runtime1->next_workspace_user, nullptr); ASSERT_EQ(workspace->ref_count, 2); xnn_delete_runtime(auto_runtime1.release()); ASSERT_EQ(workspace->first_user, nullptr); ASSERT_EQ(workspace->ref_count, 1); } TEST(WORKSPACE, zero_sized_workspace_for_graph_without_internal_tensors) { xnn_initialize(/*allocator=*/nullptr); xnn_workspace_t workspace = nullptr; xnn_create_workspace(&workspace); std::unique_ptr auto_workspace(workspace, xnn_release_workspace); std::array dims = {2, 20, 20, 3}; xnn_subgraph_t subgraph = nullptr; DefineGraphWithoutInternalTensors(&subgraph, dims); std::unique_ptr auto_subgraph(subgraph, xnn_delete_subgraph); xnn_runtime_t runtime = nullptr; ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph, nullptr, workspace, nullptr, 0, &runtime)); std::unique_ptr auto_runtime(runtime, xnn_delete_runtime); ASSERT_EQ(0, workspace->size); ASSERT_EQ(nullptr, workspace->data); ASSERT_EQ(std::vector({runtime}), workspace_user_to_list(workspace)); ASSERT_EQ(workspace->ref_count, 2); }