1// Copyright (c) 2022 Graphcore Ltd. All rights reserved.
2
3#pragma GCC diagnostic ignored "-Wshadow"
4
5#include <chrono>
6#include <cstdlib>
7#include <numeric>
8#include <string>
9#include <vector>
10
11#include <boost/filesystem.hpp>
12#include <boost/program_options.hpp>
13
14#include <popops/ElementWise.hpp>
15#include <popops/codelets.hpp>
16
17#include <popef/Types.hpp>
18#include <popef/Writer.hpp>
19#include <poplar/DeviceManager.hpp>
20#include <poplar/Engine.hpp>
21#include <poplar/Graph.hpp>
22
23#include "utils.hpp"
24namespace examples {
25
26boost::program_options::variables_map parseProgramOptions(int argc,
27 char *argv[]);
28
29} // namespace examples
30
31// Example generetes simple popef file performing following operation
32// output = (A * weights) + B
33// A, B -> model inputs
34// weights -> tensor_data saved in popef file
35int main(int argc, char *argv[]) {
36 using namespace std::chrono_literals;
37
38 const boost::program_options::variables_map vm =
39 examples::parseProgramOptions(argc, argv);
40 const boost::filesystem::path popef_path{vm["popef_path"].as<std::string>()};
41
42 if (!boost::filesystem::exists(popef_path))
43 throw std::runtime_error(
44 fmt::format("Provided popef_path {} not exists", popef_path));
45
46 if (!boost::filesystem::is_directory(popef_path))
47 throw std::runtime_error(
48 fmt::format("Provided popef_path {} is not a directory", popef_path));
49
50 const boost::filesystem::path full_popef_path =
51 boost::filesystem::canonical(popef_path) /
52 fmt::format("{}.popef", vm["popef_filename"].as<std::string>());
53
54 if (boost::filesystem::exists(full_popef_path)) {
55 examples::print(
56 fmt::format("Path {} exists. PopEF already created.", full_popef_path));
57 return EXIT_SUCCESS;
58 }
59
60 static constexpr auto num_ipus = 1;
61 static constexpr const char *tensor_a_name = "tensor_A";
62 static constexpr const char *tensor_b_name = "tensor_B";
63 static constexpr const char *weights_name = "weights";
64 static constexpr const char *output_name = "output";
65
66 const auto ipuTarget = poplar::DeviceManager{}
67 .getDevices(poplar::TargetType::IPU, num_ipus)
68 .front()
69 .getTarget();
70 const auto shape = vm["shape"].as<std::vector<std::size_t>>();
71 const auto data_size = std::accumulate(shape.cbegin(), shape.cend(), 1,
72 std::multiplies<std::size_t>());
73 const std::vector<float> weight_data(data_size, 10.0f);
74
75 // Create Poplar program
76 poplar::Graph graph(ipuTarget, poplar::replication_factor(1));
77 popops::addCodelets(graph);
78 poplar::program::Sequence load_prog;
79 poplar::program::Sequence main_prog;
80
81 // Construct the poplar graph
82 const poplar::Tensor t_weights = graph.addVariable(poplar::FLOAT, shape);
83 const poplar::Tensor t_a = graph.addVariable(poplar::FLOAT, shape);
84 const poplar::Tensor t_b = graph.addVariable(poplar::FLOAT, shape);
85
86 graph.setTileMapping(t_a, 0);
87 graph.setTileMapping(t_b, 0);
88 graph.setTileMapping(t_weights, 0);
89
90 const poplar::DataStream weights_data_ds = graph.addHostToDeviceFIFO(
91 weights_name, poplar::FLOAT, t_weights.numElements());
92 load_prog.add(poplar::program::Copy(weights_data_ds, t_weights));
93
94 const poplar::DataStream a_ds = graph.addHostToDeviceFIFO(
95 tensor_a_name, poplar::FLOAT, t_a.numElements());
96 const poplar::DataStream b_ds = graph.addHostToDeviceFIFO(
97 tensor_b_name, poplar::FLOAT, t_b.numElements());
98
99 main_prog.add(poplar::program::Copy(a_ds, t_a));
100 main_prog.add(poplar::program::Copy(b_ds, t_b));
101
102 const auto t_out = popops::mul(
103 graph, popops::mul(graph, t_a, t_weights, main_prog), t_b, main_prog);
104 const poplar::DataStream out_ds = graph.addDeviceToHostFIFO(
105 output_name, poplar::FLOAT, t_out.numElements());
106
107 main_prog.add(poplar::program::Copy(t_out, out_ds));
108 const auto program =
109 std::vector<poplar::program::Program>{load_prog, main_prog};
110
111 poplar::Engine engine(graph, program);
112
113 static constexpr int load_program_idx = 0;
114 static constexpr int main_program_idx = 1;
115
116 // Create PopEF file
117 popef::FileWriter writer(full_popef_path.string());
118 popef::Anchor t_a_anchor;
119 popef::Anchor t_b_anchor;
120
121 popef::Anchor t_weights_anchor;
122 popef::Anchor t_out_anchor;
123 popef::Metadata meta;
124
125 const auto init_anchor =
126 [&](popef::Anchor &anchor, const std::string &name,
127 popef::TensorType type,
128 const std::vector<popef::ProgramFlow::ProgramIndexType> &programs) {
129 anchor.setName(name);
130 anchor.setHandle(name);
131 anchor.tensorInfo().setShape(
132 std::vector<int64_t>(shape.begin(), shape.end()));
133 anchor.tensorInfo().setDataType(popef::DataType::F32);
134 anchor.setType(type);
135 anchor.setPrograms(programs);
136 anchor.setIsPerReplica(type == popef::TensorType::OUTPUT);
137 anchor.setRepeats(1);
138 anchor.setUseRemoteBuffers(false);
139 };
140
141 const auto exe = writer.createExecutable("exe");
142 engine.serializeExecutable(exe->stream);
143
144 // Initialise all anchors
145 init_anchor(t_a_anchor, tensor_a_name, popef::TensorType::INPUT,
146 {main_program_idx});
147 init_anchor(t_b_anchor, tensor_b_name, popef::TensorType::INPUT,
148 {main_program_idx});
149 init_anchor(t_out_anchor, output_name, popef::TensorType::OUTPUT,
150 {main_program_idx});
151 init_anchor(t_weights_anchor, weights_name, popef::TensorType::INPUT,
152 {load_program_idx});
153
154 // Create PopEF metadata
155 meta.setNumIpus(1);
156 meta.setIpuVersion(2);
157 meta.setReplicationFactor(1);
158 meta.setExecutable("exe");
159 meta.setAnchors({t_a_anchor, t_b_anchor, t_weights_anchor, t_out_anchor});
160 // Write the metadata
161 auto &flow = meta.programFlow();
162 flow.setLoad({load_program_idx});
163 flow.setMain({main_program_idx});
164 meta.setProgramsMap({{load_program_idx, "load"}, {main_program_idx, "main"}});
165 writer.write(meta);
166
167 // Create weigths tensor data
168 popef::TensorDataInfo tensor_data_info;
169
170 tensor_data_info.setName(weights_name);
171 tensor_data_info.setTensorInfo(t_weights_anchor.tensorInfo());
172 const auto tensor_data = writer.createTensorData(tensor_data_info);
173 // Write the tensor data
174 tensor_data->stream.write(reinterpret_cast<const char *>(weight_data.data()),
175 t_weights_anchor.tensorInfo().sizeInBytes());
176
177 writer.close();
178 return EXIT_SUCCESS;
179}
180
181namespace examples {
182
183boost::program_options::variables_map parseProgramOptions(int argc,
184 char *argv[]) {
185 using namespace boost::program_options;
186 variables_map vm;
187
188 try {
189 options_description desc{"Generator example popef file - model (simple "
190 "output = (A * weights) + B)"};
191 desc.add_options()("help,h", "Help screen")(
192 "shape,s", value<std::vector<std::size_t>>()->required()->multitoken(),
193 "Input tensor shape.")("popef_path,p",
194 value<std::string>()->default_value("."),
195 "Destination PopEF path")(
196 "popef_filename,n",
197 value<std::string>()->default_value("model_runtime_example"),
198 "PopEF filename path");
199
200 store(parse_command_line(argc, argv, desc), vm);
201 notify(vm);
202 if (vm.count("help")) {
203 fmt::print("{}\n", desc);
204 exit(EXIT_SUCCESS);
205 }
206 } catch (const error &ex) {
207 examples::print_err(ex.what());
208 exit(EXIT_FAILURE);
209 }
210
211 return vm;
212}
213
214} // namespace examples