Introduction
This tutorial shows you how to create a simple kernel which flows in a tensor from DDR into the Array via the L2 memory, then performs operations on the tensor, and finally flows the tensor back out to DDR memory.
Refer to the Chimera Software API Reference guide for more information on the APIs described in this section.
Requirements
You need to first install the Quadric SDK.
Getting Started
Follow the steps below to create a simple kernel:
- Add the appropriate Quadric C++ includes to the top of your
.cppfile:
File:
/tests/static_data_tests/simple_flow.cppLines 19–23//! [Adding Quadric C++ Headers]
// Include quadric Intermediate Language Header.
#include <quadric/host.h>
#include <quadric/qil.h>
using namespace chimera;
- Define the shapes of the tensors you wish to flow into the Chimera processor:
File:
tests/static_data_tests/simple_flow.cppLines 26–42constexpr std::uint32_t height = core_array::coreDim;
constexpr std::uint32_t width = core_array::coreDim;
constexpr std::uint32_t depth = 1;
constexpr std::uint32_t batchSize = 1;
//! [Adding Tensor Defs]
/*
* Define shapes of Tensors, Note there's a DDR Tensor
* and also an OcmTensor shape. The types DdrInOutShape and OcmInOutShape
* contain the attributes of the data in DDR and OCM respectively.
*/
typedef DdrTensor<std::int32_t, batchSize, depth, height, width> DdrInOutShape;
typedef OcmTensor<std::int32_t, batchSize, depth, height, width> OcmInOutShape;
//! [Adding Tensor Defs]
//! [OcmRoi]
typedef OcmTensor<std::int32_t, batchSize, depth, height, width> minRoiInOutShape;
- Define the CPU version of the algorithm you want to implement. You can use this as a reference to measure the accuracy of the quadric model.
File:
/tests/static_data_tests/simple_flow.cppLines 111–125/*
Implement CPU version of the algorithm
This can be used as a reference to validate the quadric generated output.
*/
#ifndef __epu__
void generateOutput(DdrInOutShape& ddrInputImage, DdrInOutShape& ddrOutputImage) {
auto ddrInp = DdrInOutShape::cast(ddrInputImage);
auto ddrOut = DdrInOutShape::cast(ddrOutputImage);
for(std::int32_t y = 0; y < height; ++y) {
for(std::int32_t x = 0; x < width; ++x) {
(*ddrOut)[0][0][y][x] = (*ddrInp)[0][0][y][x] + 1;
}
}
}
#endif
- Define a kernel function with a return type of
voidand DDR tensors are passed as pointers arguments:
File:
/tests/static_data_tests/simple_flow.cppLine 48EPU_ENTRY void myKernel(DdrInOutShape::ptrType ddrInpPtr, DdrInOutShape::ptrType ddrOutPtr) {
- DDR tensors are passed in as pointers, so recreate the tensor object as shown below:
File:
/tests/static_data_tests/simple_flow.cppLines 53–55 //! [Recreate Tensor Obj]
DdrInOutShape ddrInp(ddrInpPtr);
DdrInOutShape ddrOut(ddrOutPtr);
- Create L2 memory tensors and allocate them:
File:
/tests/static_data_tests/simple_flow.cppLines 58–64 //! [Create OcmTensors]
OcmInOutShape ocmInp;
OcmInOutShape ocmOut;
// Create an instance of the On Chip Memory (OCM) Memory Allocator
MemAllocator ocmMem;
ocmMem.allocate(ocmInp);
ocmMem.allocate(ocmOut);
- Copy memory data from DDR to OCM using
memCpyin the memCpy API:
File:
/tests/static_data_tests/simple_flow.cppLines 67–68 //! [Do inbound Memcpy]
memCpy(ddrInp, ocmInp);
- Define
TensorAccessorsfor reading and writing:
File:
/tests/static_data_tests/simple_flow.cppLines 71–89- In this special case, the shapes of both our input and output tensors are the same (minRoiInOutShape), so we can define a single 'TensorAccessor' for reading and writing (input and output).
/*The first parameter of TensorAccessor is MinRoiDescriptor.
This object specifies;
1) Shape of the MinROI
2) AxisGroup, how the data is arranged within the MinROI,(Order of the Axis priority)
3) Granularity shape: Square or Row
4) Border: Whether we want data in border cores.
If the input and output shapes are different, you need to define two MinRoiDescriptor.
*/
using MroiDescp =
MinRoiDescriptor<minRoiInOutShape, AxisGroup<Direction::Height, Direction::Width>, Granularity::Square, true>;
/*
A TensorAccessor object expects two parameters.
1) MinRoiDescriptor: The MinRoiDescriptor.(See the above definition)
2) RoiAxisGroup: The RoiAxisGroup indicates the moving pattern for the MinROI inside the ROI.
If the input and output shapes are different, you need to define two TensorAccessors.
*/
using InOutAccessor = TensorAccessor<MroiDescp, AxisGroup<Direction::Width>>;
- Define flow objects for reading and writing data:
File:
/tests/static_data_tests/simple_flow.cppLines 91–95 /*
Define flow objects for reading and writing
*/
ReadFlow<InOutAccessor, OcmInOutShape> readFlow(ocmInp);
WriteFlow<InOutAccessor, OcmInOutShape> writeFlow(ocmOut);
- Create
NDArrayObjects to store tiles, and flow in data from/to L2 memory to/from the LRM (Local Register Memory) Array:
File:
/tests/static_data_tests/simple_flow.cppLines 98–99 auto qIn = readFlow.read();
WriteFlow<InOutAccessor, minRoiInOutShape>::MinRoiArray qOut;
- Perform an operation on each tile of data. In the example below, a 1 is added to each tile of data:
File:
/tests/static_data_tests/simple_flow.cppLines 101–103 for(std::size_t tileIdx = 0; tileIdx < qIn.size(); ++tileIdx) {
qOut[tileIdx] = qIn[tileIdx] + 1;
}
- Flow out data from the array to the L2 memory:
File:
/tests/static_data_tests/simple_flow.cppLine 104 writeFlow.write(qOut);
- Finally, write the data to DDR:
File:
/tests/static_data_tests/simple_flow.cppLines 106–107 //! [Write to DDR]
memCpy(ocmOut, ddrOut);
- Perform host-side set up to launch the kernel with data:
File:
/tests/static_data_tests/simple_flow.cppLines 129–174HOST_MAIN(
// Create DDR Tensors on the host computer, one for input, one for output,
// one for expected output(from the CPU model)
DdrInOutShape ddrInPtr; DdrInOutShape ddrOutPtr; DdrInOutShape ddrOutExp;
// Allocate tensors on the host computer.
DdrInOutShape::allocate(ddrInPtr);
DdrInOutShape::allocate(ddrOutPtr);
DdrInOutShape::allocate(ddrOutExp);
// Populate the Tensor sequentially.
populateTensorSequential(ddrInPtr);
generateOutput(ddrInPtr, ddrOutExp);
// Wrap the Tensor in a TensorArg meant to pass to the kernel.
TensorArg<DdrInOutShape> inputArg{&ddrInPtr};
TensorArg<DdrInOutShape> outputArg{&ddrOutPtr};
//! [Call Kernel]
packageKernel(OUTPUT_PREFIX, FUNC_ARG(myKernel), inputArg, outputArg);
DeviceBufferRef in, out;
auto device = DeviceManager().getDevice(callKernelGlobals.deviceConfig);
device.loadKernel();
device.allocateAndCopyToDevice(ddrInPtr, in);
device.allocate<DdrInOutShape>(out);
device.runKernel(ENTRYPOINT(myKernel), in, out);
device.copyBufferFromDevice(out, ddrOutPtr);
//! [Call Kernel]
// Print execution cycle count breakdown
device.printProfile();
// Compare results
auto nativeCompareVisitor =
[](chimera-software-user-guide/chimera-compute-library-ccl-api/const auto& t1, const auto& t2, const DimensionContext& context) {
return nativeCompare<DdrInOutShape>(t1, t2, context, 1);
};
runtime_assert(compareTensors(ddrOutPtr, ddrOutExp, nativeCompareVisitor), "tensor mismatch");
);
- Launch the kernel using the SDK:
$ quadric sdk source simple_flow.cpp
The following example shows the complete code for a kernel that includes all of the steps described above: (sdk) tests/static_data_tests/simple_flow.cpp