blob: 04c38a9169c9e2b1be16671c31ac66a36073a9e1 [file] [log] [blame]
/*
* Copyright (c) 2025 The Khronos Group Inc.
* Copyright (C) 2025 Arm Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
#include "data_graph_objects.h"
#include "binding.h"
#include "generated/pnext_chain_extraction.h"
#include <iostream>
namespace vkt {
namespace dg {
void DataGraphPipelineHelper::CreateShaderModule(const char* spirv_source, const char* entrypoint) {
spvtools::SpirvTools tools{SPV_ENV_UNIVERSAL_1_6};
std::string error_msg;
tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t& position, const char* message) {
std::ostringstream ss;
ss << "on line " << position.line << ", column " << position.column << ": " << message;
error_msg = ss.str();
});
// TODO - Replace with ASMtoSPV
std::vector<uint32_t> spirv_binary;
if (!tools.Assemble(spirv_source, &spirv_binary)) {
GTEST_FAIL() << "Failed to compile SPIRV shader module. Error:\n" << error_msg << std::endl
<< "SpirV:\n" << spirv_source << std::endl;
}
VkShaderModuleCreateInfo shader_module_create_info = vku::InitStructHelper();
shader_module_create_info.codeSize = spirv_binary.size() * sizeof(uint32_t);
shader_module_create_info.pCode = spirv_binary.data();
shader_.Init(*device_, shader_module_create_info);
shader_module_ci_ = vku::InitStructHelper();
shader_module_ci_.module = shader_;
shader_module_ci_.pName = entrypoint ? entrypoint : "main";
vvl::PnextChainAdd(&pipeline_ci_, &shader_module_ci_);
}
std::string DataGraphPipelineHelper::GetSpirvMultiEntryTwoDataGraph() {
return R"(
OpCapability GraphARM
OpCapability TensorsARM
OpCapability Int8
OpCapability Shader
OpCapability VulkanMemoryModel
OpCapability Matrix
OpExtension "SPV_ARM_graph"
OpExtension "SPV_ARM_tensors"
OpExtension "SPV_KHR_vulkan_memory_model"
%tosa = OpExtInstImport "TOSA.001000.1"
OpMemoryModel Logical Vulkan
OpName %main_arg_0 "main_arg_0"
OpName %main_res_0 "main_res_0"
OpDecorate %main_arg_0 Binding 0
OpDecorate %main_arg_0 DescriptorSet 0
OpDecorate %main_res_0 Binding 1
OpDecorate %main_res_0 DescriptorSet 0
%uchar = OpTypeInt 8 0
%uint = OpTypeInt 32 0
%uchar_0 = OpConstant %uchar 0
%uint_4 = OpConstant %uint 4
%uint_1 = OpConstant %uint 1
%uint_8 = OpConstant %uint 8
%uint_16 = OpConstant %uint 16
%uint_0 = OpConstant %uint 0
%uint_2 = OpConstant %uint 2
%uint_arr_4 = OpTypeArray %uint %uint_4
%uint_arr_1 = OpTypeArray %uint %uint_1
%uint_arr_4_1_8_16_4 = OpConstantComposite %uint_arr_4 %uint_1 %uint_8 %uint_16 %uint_4
%uint_arr_4_1_2_4_4 = OpConstantComposite %uint_arr_4 %uint_1 %uint_2 %uint_4 %uint_4
%uint_arr_4_1_4_8_4 = OpConstantComposite %uint_arr_4 %uint_1 %uint_4 %uint_8 %uint_4
%uint_arr_1_1 = OpConstantComposite %uint_arr_1 %uint_1
%uint_arr_1_2 = OpConstantComposite %uint_arr_1 %uint_2
%uint_arr_1_4 = OpConstantComposite %uint_arr_1 %uint_4
%uchar_1_8_16_4_tensor = OpTypeTensorARM %uchar %uint_4 %uint_arr_4_1_8_16_4
%uchar_1_2_4_4_tensor = OpTypeTensorARM %uchar %uint_4 %uint_arr_4_1_2_4_4
%uchar_1_4_8_4_tensor = OpTypeTensorARM %uchar %uint_4 %uint_arr_4_1_4_8_4
%uchar_1_tensor = OpTypeTensorARM %uchar %uint_1 %uint_arr_1_1
%uint_2_tensor = OpTypeTensorARM %uint %uint_1 %uint_arr_1_2
%uint_4_tensor = OpTypeTensorARM %uint %uint_1 %uint_arr_1_4
%uint_2_tensor_2_2 = OpConstantComposite %uint_2_tensor %uint_2 %uint_2
%uint_4_tensor_0_0_0_0 = OpConstantComposite %uint_4_tensor %uint_0 %uint_0 %uint_0 %uint_0
%uchar_1_tensor_0 = OpConstantComposite %uchar_1_tensor %uchar_0
%uchar_1_8_16_4_tensor_ptr = OpTypePointer UniformConstant %uchar_1_8_16_4_tensor
%uchar_1_2_4_4_tensor_ptr = OpTypePointer UniformConstant %uchar_1_2_4_4_tensor
; constant used ONLY in entrypoint 2
%constant0 = OpGraphConstantARM %uchar_1_2_4_4_tensor 0
%main_arg_0 = OpVariable %uchar_1_8_16_4_tensor_ptr UniformConstant
%main_res_0 = OpVariable %uchar_1_2_4_4_tensor_ptr UniformConstant
%graph_type = OpTypeGraphARM 1 %uchar_1_8_16_4_tensor %uchar_1_2_4_4_tensor
; graph 1: %main_res_0 = MAX_POOL2D(MAX_POOL2D(%main_arg_0))
%graph_1 = OpGraphARM %graph_type
%in_0 = OpGraphInputARM %uchar_1_8_16_4_tensor %uint_0
%op_0 = OpExtInst %uchar_1_4_8_4_tensor %tosa MAX_POOL2D %uint_2_tensor_2_2 %uint_2_tensor_2_2 %uint_4_tensor_0_0_0_0 %uint_0 %in_0
%op_1 = OpExtInst %uchar_1_2_4_4_tensor %tosa MAX_POOL2D %uint_2_tensor_2_2 %uint_2_tensor_2_2 %uint_4_tensor_0_0_0_0 %uint_0 %op_0
OpGraphSetOutputARM %op_1 %uint_0
OpGraphEndARM
; graph 2: %main_res_0 = ADD(AVG_POOL2D(AVG_POOL2D(%main_arg_0)), %constant0)
%graph_2 = OpGraphARM %graph_type
%in_1 = OpGraphInputARM %uchar_1_8_16_4_tensor %uint_0
%op_2 = OpExtInst %uchar_1_4_8_4_tensor %tosa AVG_POOL2D %uint_2_tensor_2_2 %uint_2_tensor_2_2 %uint_4_tensor_0_0_0_0 %uint_1 %in_1 %uchar_1_tensor_0 %uchar_1_tensor_0
%op_3 = OpExtInst %uchar_1_2_4_4_tensor %tosa AVG_POOL2D %uint_2_tensor_2_2 %uint_2_tensor_2_2 %uint_4_tensor_0_0_0_0 %uint_1 %op_2 %uchar_1_tensor_0 %uchar_1_tensor_0
%op_4 = OpExtInst %uchar_1_2_4_4_tensor %tosa ADD %op_3 %constant0
OpGraphSetOutputARM %op_4 %uint_0
OpGraphEndARM
; bind graphs to entrypoints
OpGraphEntryPointARM %graph_1 "entrypoint_1" %main_arg_0 %main_res_0
OpGraphEntryPointARM %graph_2 "entrypoint_2" %main_arg_0 %main_res_0
)";
}
// Spirv source. For testing purposes it includes:
// - unused OpGraphConstantARM
// - `inserted_line` to cause different errors
std::string DataGraphPipelineHelper::GetSpirvModifyableDataGraph(const ModifiableShaderParameters& params) {
std::ostringstream ss;
ss << R"(
OpCapability GraphARM
OpCapability TensorsARM
)" << params.capabilities << R"(
OpCapability Int8
OpCapability Shader
OpCapability VulkanMemoryModel
OpCapability Matrix
OpExtension "SPV_ARM_graph"
OpExtension "SPV_ARM_tensors"
OpExtension "SPV_KHR_vulkan_memory_model"
%tosa = OpExtInstImport "TOSA.001000.1"
OpMemoryModel Logical Vulkan
OpName %main_arg_0 "main_arg_0"
OpName %main_res_0 "main_res_0"
OpDecorate %main_arg_0 Binding 0
OpDecorate %main_arg_0 DescriptorSet 0
OpDecorate %main_res_0 Binding 1
OpDecorate %main_res_0 DescriptorSet 0
%uchar = OpTypeInt 8 0
%uint = OpTypeInt 32 0
%uint_4 = OpConstant %uint 4
%uint_1 = OpConstant %uint 1
%uint_8 = OpConstant %uint 8
%uint_16 = OpConstant %uint 16
%uint_0 = OpConstant %uint 0
%uint_2 = OpConstant %uint 2
%uint_arr_4 = OpTypeArray %uint %uint_4
%uint_arr_1 = OpTypeArray %uint %uint_1
%uint_arr_4_1_8_16_4 = OpConstantComposite %uint_arr_4 %uint_1 %uint_8 %uint_16 %uint_4
%uint_arr_4_1_2_4_4 = OpConstantComposite %uint_arr_4 %uint_1 %uint_2 %uint_4 %uint_4
%uint_arr_4_1_4_8_4 = OpConstantComposite %uint_arr_4 %uint_1 %uint_4 %uint_8 %uint_4
%uint_arr_1_2 = OpConstantComposite %uint_arr_1 %uint_2
%uint_arr_1_4 = OpConstantComposite %uint_arr_1 %uint_4
%uchar_1_8_16_4_tensor = OpTypeTensorARM %uchar %uint_4 %uint_arr_4_1_8_16_4
%uchar_1_2_4_4_tensor = OpTypeTensorARM %uchar %uint_4 %uint_arr_4_1_2_4_4
%uchar_1_4_8_4_tensor = OpTypeTensorARM %uchar %uint_4 %uint_arr_4_1_4_8_4
%uint_2_tensor = OpTypeTensorARM %uint %uint_1 %uint_arr_1_2
%uint_4_tensor = OpTypeTensorARM %uint %uint_1 %uint_arr_1_4
)" << params.types << R"(
%uint_2_tensor_2_2 = OpConstantComposite %uint_2_tensor %uint_2 %uint_2
%uint_4_tensor_0_0_0_0 = OpConstantComposite %uint_4_tensor %uint_0 %uint_0 %uint_0 %uint_0
%uchar_1_8_16_4_tensor_ptr = OpTypePointer UniformConstant %uchar_1_8_16_4_tensor
%uchar_1_2_4_4_tensor_ptr = OpTypePointer UniformConstant %uchar_1_2_4_4_tensor
%main_arg_0 = OpVariable %uchar_1_8_16_4_tensor_ptr UniformConstant
%main_res_0 = OpVariable %uchar_1_2_4_4_tensor_ptr UniformConstant
%graph_type = OpTypeGraphARM 1 %uchar_1_8_16_4_tensor %uchar_1_2_4_4_tensor
OpGraphEntryPointARM %graph_0 "main" %main_arg_0 %main_res_0
%graph_0 = OpGraphARM %graph_type
%in_0 = OpGraphInputARM %uchar_1_8_16_4_tensor %uint_0
%op_0 = OpExtInst %uchar_1_4_8_4_tensor %tosa MAX_POOL2D %uint_2_tensor_2_2 %uint_2_tensor_2_2 %uint_4_tensor_0_0_0_0 %uint_0 %in_0
%op_1 = OpExtInst %uchar_1_2_4_4_tensor %tosa MAX_POOL2D %uint_2_tensor_2_2 %uint_2_tensor_2_2 %uint_4_tensor_0_0_0_0 %uint_0 %op_0
)" << params.instructions << R"(
OpGraphSetOutputARM %op_1 %uint_0
OpGraphEndARM
)";
return ss.str();
}
// A command shader (for the majority of cases) that can be modified inserting
// instructions in given sections. Without any insertions it's a basic shader.
std::string DataGraphPipelineHelper::GetSpirvModifiableShader(const ModifiableShaderParameters& params) {
std::ostringstream ss;
ss << R"(
; SPIRV
; Version: 1.6
; Generator: Khronos Glslang Reference Front End; 11
; Bound: 19
; Schema: 0
OpCapability Shader
)" << params.capabilities << R"(
OpCapability TensorsARM
OpExtension "SPV_ARM_tensors"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %tens
OpExecutionMode %main LocalSize 1 1 1
OpSource GLSL 450
OpSourceExtension "GL_ARM_tensors"
OpSourceExtension "GL_EXT_shader_explicit_arithmetic_types"
OpName %main "main"
OpName %size_x "size_x"
OpName %tens "tens"
OpDecorate %tens Binding 0
OpDecorate %tens DescriptorSet 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%uint = OpTypeInt 32 0
%_ptr_Function_uint = OpTypePointer Function %uint
%int = OpTypeInt 32 1
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%11 = OpTypeTensorARM %int %uint_1
)" << params.types << R"(
%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11
%tens = OpVariable %_ptr_UniformConstant_11 UniformConstant
%v3uint = OpTypeVector %uint 3
%18 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
%main = OpFunction %void None %3
%5 = OpLabel
%size_x = OpVariable %_ptr_Function_uint Function
%loaded_tens = OpLoad %11 %tens
%16 = OpTensorQuerySizeARM %uint %loaded_tens %uint_0
OpStore %size_x %16
)" << params.instructions << R"(
OpReturn
OpFunctionEnd
)";
return ss.str();
}
// spirv using a descriptor array
std::string DataGraphPipelineHelper::GetSpirvTensorArrayDataGraph(bool is_runtime) {
std::ostringstream ss;
ss << R"(
OpCapability GraphARM
OpCapability TensorsARM
)" << (is_runtime ? "OpCapability RuntimeDescriptorArray" : "") << R"(
OpCapability Int8
OpCapability Shader
OpCapability VulkanMemoryModel
OpCapability Matrix
OpExtension "SPV_ARM_graph"
OpExtension "SPV_ARM_tensors"
OpExtension "SPV_KHR_vulkan_memory_model"
%tosa = OpExtInstImport "TOSA.001000.1"
OpMemoryModel Logical Vulkan
OpName %main_arg_0 "main_arg_0"
OpName %main_res_0 "main_res_0"
OpDecorate %main_arg_0 Binding 0
OpDecorate %main_arg_0 DescriptorSet 0
OpDecorate %main_res_0 Binding 1
OpDecorate %main_res_0 DescriptorSet 0
%i8 = OpTypeInt 8 0
%i32 = OpTypeInt 32 0
%i32_0 = OpConstant %i32 0
%i32_1 = OpConstant %i32 1
%i32_2 = OpConstant %i32 2
%i32_4 = OpConstant %i32 4
%i32_arr_4 = OpTypeArray %i32 %i32_4
%tensor_shape = OpConstantComposite %i32_arr_4 %i32_1 %i32_4 %i32_4 %i32_2
%tensor = OpTypeTensorARM %i8 %i32_4 %tensor_shape
)" << (is_runtime ? "%tensor_array = OpTypeRuntimeArray %tensor" : "%tensor_array = OpTypeArray %tensor %i32_2") << R"(
%ptr_tensor_array = OpTypePointer UniformConstant %tensor_array
%ptr_tensor = OpTypePointer UniformConstant %tensor
%main_arg_0 = OpVariable %ptr_tensor_array UniformConstant
%main_res_0 = OpVariable %ptr_tensor UniformConstant
%graph_type = OpTypeGraphARM 1 %tensor_array %tensor
OpGraphEntryPointARM %graph_0 "main" %main_arg_0 %main_res_0
%graph_0 = OpGraphARM %graph_type
%in_0 = OpGraphInputARM %tensor %i32_0 %i32_0
%in_1 = OpGraphInputARM %tensor %i32_0 %i32_1
%out_0 = OpExtInst %tensor %tosa ADD %in_0 %in_1
OpGraphSetOutputARM %out_0 %i32_0
OpGraphEndARM
)";
return ss.str();
}
std::string DataGraphPipelineHelper::GetSpirvConstantDataGraph() {
vkt::dg::ModifiableShaderParameters spirv_params;
spirv_params.types = "%constant_0 = OpGraphConstantARM %uchar_1_2_4_4_tensor 0";
spirv_params.instructions = "%dummy = OpExtInst %uchar_1_2_4_4_tensor %tosa ADD %op_1 %constant_0";
return vkt::dg::DataGraphPipelineHelper::GetSpirvModifyableDataGraph(spirv_params);
}
// shapes for 2-layer maxpool 2x2: output tensor is 1/4 the size of the input tensor
const std::vector<int64_t> in_tensor_dims = {1, 8, 16, 4};
const std::vector<int64_t> out_tensor_dims = {in_tensor_dims[0], in_tensor_dims[1] / 4, in_tensor_dims[2] / 4, in_tensor_dims[3]};
// shape for ADD spirv
const std::vector<int64_t> add_dimensions{1, 4, 4, 2};
void DataGraphPipelineHelper::InitPipelineResources(VkDescriptorType desc_type) {
if (params_.graph_variant == AddTensorArraySpirv || params_.graph_variant == AddRuntimeTensorArraySpirv) {
// tensors for GetSpirvTensorArrayDataGraph(): array of 2 input, 1 output
VkTensorDescriptionARM desc = vku::InitStructHelper();
desc.tiling = VK_TENSOR_TILING_LINEAR_ARM;
desc.format = VK_FORMAT_R32_SINT;
desc.dimensionCount = add_dimensions.size();
desc.pDimensions = add_dimensions.data();
desc.pStrides = nullptr;
desc.usage = VK_TENSOR_USAGE_DATA_GRAPH_BIT_ARM;
tensors_.resize(3);
tensors_[0] = std::make_shared<vkt::Tensor>(*device_, desc);
tensors_[1] = std::make_shared<vkt::Tensor>(*device_, desc);
tensors_[2] = std::make_shared<vkt::Tensor>(*device_, desc);
resources_.resize(3);
// last 3 numbers are: descriptor, binding, array index
// 2 input tensors, as array
resources_[0] = {VK_STRUCTURE_TYPE_DATA_GRAPH_PIPELINE_RESOURCE_INFO_ARM, &tensors_[0]->Description(), 0, 0, 0};
resources_[1] = {VK_STRUCTURE_TYPE_DATA_GRAPH_PIPELINE_RESOURCE_INFO_ARM, &tensors_[1]->Description(), 0, 0, 1};
// 1 output tensor
resources_[2] = {VK_STRUCTURE_TYPE_DATA_GRAPH_PIPELINE_RESOURCE_INFO_ARM, &tensors_[2]->Description(), 1, 0, 0};
// binding 0: 2 x inputs; binding 1: 1 x output
descriptor_set_layout_bindings_.resize(2);
descriptor_set_layout_bindings_[0] = {0, desc_type, 2, VK_SHADER_STAGE_ALL, nullptr};
descriptor_set_layout_bindings_[1] = {1, desc_type, 1, VK_SHADER_STAGE_ALL, nullptr};
} else { // default: BasicSpirv
// tensors for GetSpirvModifyableDataGraph(): 1 input, 1 output
tensors_.resize(2);
tensor_views_.resize(tensors_.size());
descriptor_set_layout_bindings_.resize(tensors_.size());
resources_.resize(tensors_.size());
for (uint32_t i = 0; i < tensors_.size(); i++) {
tensors_[i] = std::make_shared<vkt::Tensor>();
tensor_views_[i] = std::make_shared<vkt::TensorView>();
const std::vector<int64_t>& dims = (i == 0) ? in_tensor_dims : out_tensor_dims;
InitTensor(*tensors_[i], *tensor_views_[i], dims, params_.protected_tensors);
// last 3 numbers are: descriptor, binding, array index
resources_[i] = {VK_STRUCTURE_TYPE_DATA_GRAPH_PIPELINE_RESOURCE_INFO_ARM, &tensors_[i]->Description(), 0, i, 0};
descriptor_set_layout_bindings_[i] = {i, desc_type, 1, VK_SHADER_STAGE_ALL, nullptr};
}
}
pipeline_ci_.resourceInfoCount = resources_.size();
pipeline_ci_.pResourceInfos = resources_.data();
descriptor_set_.reset(new OneOffDescriptorSet(device_, descriptor_set_layout_bindings_));
CreatePipelineLayout();
}
void DataGraphPipelineHelper::CreatePipelineLayout(const std::vector<VkPushConstantRange>& push_constant_ranges) {
pipeline_layout_ci_ = vku::InitStructHelper();
pipeline_layout_ci_.flags = 0;
pipeline_layout_ci_.pushConstantRangeCount = push_constant_ranges.size();
pipeline_layout_ci_.pPushConstantRanges = push_constant_ranges.data();
pipeline_layout_ = vkt::PipelineLayout(*device_, pipeline_layout_ci_, {&descriptor_set_->layout_});
pipeline_ci_.layout = pipeline_layout_;
}
void DataGraphPipelineHelper::InitTensor(vkt::Tensor& tensor, vkt::TensorView& tensor_view, const std::vector<int64_t>& tensor_dims,
bool is_protected) {
VkTensorDescriptionARM tensor_desc = vku::InitStructHelper();
tensor_desc.tiling = VK_TENSOR_TILING_LINEAR_ARM;
tensor_desc.format = VK_FORMAT_R8_SINT;
tensor_desc.dimensionCount = tensor_dims.size();
tensor_desc.pDimensions = tensor_dims.data();
tensor_desc.usage = VK_TENSOR_USAGE_DATA_GRAPH_BIT_ARM;
VkTensorCreateInfoARM tensor_info = vku::InitStructHelper();
tensor_info.pDescription = &tensor_desc;
tensor_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkFlags memory_flags = 0;
if (is_protected) {
tensor_info.flags |= VK_TENSOR_CREATE_PROTECTED_BIT_ARM;
memory_flags = VK_MEMORY_PROPERTY_PROTECTED_BIT;
}
tensor.InitNoMem(*device_, tensor_info);
tensor.BindToMem(memory_flags);
VkTensorViewCreateInfoARM tensor_view_ci = vku::InitStructHelper();
tensor_view_ci.tensor = tensor;
tensor_view_ci.format = tensor.Format();
tensor_view.Init(*device_, tensor_view_ci);
}
DataGraphPipelineHelper::DataGraphPipelineHelper(VkLayerTest& test, const HelperParameters& params)
: layer_test_(test), params_(params) {
device_ = layer_test_.DeviceObj();
pipeline_ci_ = vku::InitStructHelper();
std::string spirv_string(params_.spirv_source ? params_.spirv_source
: params_.graph_variant == AddTensorArraySpirv ? GetSpirvTensorArrayDataGraph(false)
: params_.graph_variant == AddRuntimeTensorArraySpirv ? GetSpirvTensorArrayDataGraph(true)
: GetSpirvBasicDataGraph());
CreateShaderModule(spirv_string.c_str(), params_.entrypoint);
InitPipelineResources(params_.desc_type);
// Check that the initialisation of the pipeline has been successful
layer_test_.Monitor().Finish();
}
VkResult DataGraphPipelineHelper::CreateDataGraphPipeline() {
return vk::CreateDataGraphPipelinesARM(device_->handle(), VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &pipeline_ci_, nullptr,
&pipeline_);
}
void DataGraphPipelineHelper::Destroy() {
if (pipeline_ != VK_NULL_HANDLE) {
vk::DestroyPipeline(device_->handle(), pipeline_, nullptr);
pipeline_ = VK_NULL_HANDLE;
}
}
DataGraphPipelineHelper::~DataGraphPipelineHelper() { Destroy(); }
} // namespace dg
} // namespace vkt