9. Appendix

9.1. Files contain helper functions used by examples

Listing 9.1 utils.hpp
  1// Copyright (c) 2022 Graphcore Ltd. All rights reserved.
  2
  3#pragma once
  4
  5#include <algorithm>
  6#include <cstdlib>
  7#include <iterator>
  8#include <sstream>
  9#include <string>
 10#include <thread>
 11#include <utility>
 12#include <vector>
 13
 14#include <boost/program_options.hpp>
 15
 16#include <spdlog/fmt/fmt.h>
 17#include <spdlog/fmt/ostr.h>
 18
 19#include "model_runtime/ModelRunner.hpp"
 20#include "model_runtime/SessionUtils.hpp"
 21#include "model_runtime/Tensor.hpp"
 22namespace examples {
 23
 24inline void print(const std::string &msg, _IO_FILE *out = stdout);
 25inline void print(const char *msg, _IO_FILE *out = stdout);
 26inline void print_err(const std::string &msg);
 27inline void print_err(const char *msg);
 28
 29inline void print(const std::string &msg, _IO_FILE *out) {
 30  print(msg.c_str(), out);
 31}
 32
 33inline void print(const char *msg, _IO_FILE *out) {
 34  fmt::print(out, "[thread:{}] {}\n", std::this_thread::get_id(), msg);
 35}
 36
 37inline void print_err(const std::string &msg) { print_err(msg.c_str()); }
 38
 39inline void print_err(const char *msg) { print(msg, stderr); }
 40
 41inline boost::program_options::variables_map
 42parsePopefProgramOptions(const char *example_desc, int argc, char *argv[]) {
 43  using namespace boost::program_options;
 44  variables_map vm;
 45
 46  try {
 47    options_description desc{example_desc};
 48    desc.add_options()("help,h", "Help screen")(
 49        "popef,p",
 50        value<std::vector<std::string>>()
 51            ->required()
 52            ->multitoken()
 53            ->composing(),
 54        "A collection of PopEF files containing the model.");
 55
 56    positional_options_description pos_desc;
 57    pos_desc.add("popef", -1);
 58
 59    command_line_parser parser{argc, argv};
 60    parser.options(desc).positional(pos_desc).allow_unregistered();
 61    parsed_options parsed_options = parser.run();
 62
 63    store(parsed_options, vm);
 64    notify(vm);
 65    if (vm.count("help")) {
 66      fmt::print("{}\n", desc);
 67      exit(EXIT_SUCCESS);
 68    }
 69  } catch (const error &ex) {
 70    examples::print_err(ex.what());
 71    exit(EXIT_FAILURE);
 72  }
 73
 74  return vm;
 75}
 76
 77// Anchor filtering predicate - assigns "bind user callback" policy to
 78// anchors loaded or saved in main_program or those that have their
 79// programs usage list empty (no info about anchor programs in PopEF)
 80inline model_runtime::AnchorCallbackPredicate
 81filterMainOrEmpty(std::shared_ptr<popef::Model> model) {
 82  static constexpr auto acceptPolicy =
 83      model_runtime::AnchorCallbackPolicy::BIND_USER_CB;
 84  static constexpr auto rejectPolicy =
 85      model_runtime::AnchorCallbackPolicy::BIND_EMPTY_CB;
 86
 87  using namespace model_runtime::predicate_factory::anchor_callbacks;
 88  return orBind(acceptPolicy, rejectPolicy,
 89                predProgramFlowMain(model->metadata.programFlow(), acceptPolicy,
 90                                    rejectPolicy),
 91                predProgramNotAssigned(acceptPolicy, rejectPolicy));
 92}
 93
 94using HostMemory = std::unordered_map<std::string, model_runtime::TensorMemory>;
 95
 96inline HostMemory
 97allocateHostData(const std::vector<model_runtime::DataDesc> &data_descs) {
 98  HostMemory host_allocated_memory;
 99  for (const model_runtime::InputDesc &input_desc : data_descs) {
100    const std::string name = input_desc.name;
101    const int64_t size_in_bytes = input_desc.size_in_bytes;
102
103    examples::print(
104        fmt::format("Allocating tensor {}, {} bytes.", name, size_in_bytes));
105    host_allocated_memory.emplace(name,
106                                  model_runtime::TensorMemory(size_in_bytes));
107  }
108
109  return host_allocated_memory;
110}
111
112inline model_runtime::InputMemory allocateHostInputData(
113    const std::vector<model_runtime::InputDesc> &input_data_descs) {
114  return allocateHostData(input_data_descs);
115}
116
117inline model_runtime::OutputMemory allocateHostOutputData(
118    const std::vector<model_runtime::OutputDesc> &output_data_descs) {
119  return allocateHostData(output_data_descs);
120}
121
122inline model_runtime::InputMemoryView
123toInputMemoryView(const model_runtime::InputMemory &input_memory) {
124  model_runtime::InputMemoryView input_memory_view;
125
126  std::transform(
127      input_memory.cbegin(), input_memory.cend(),
128      std::inserter(input_memory_view, input_memory_view.end()),
129      [](model_runtime::InputMemory::const_reference name_with_memory) {
130        auto &&[name, memory] = name_with_memory;
131        return std::make_pair(name, memory.getView());
132      });
133
134  return input_memory_view;
135}
136
137inline model_runtime::OutputMemoryView
138toOutputMemoryView(model_runtime::OutputMemory &output_memory) {
139  model_runtime::OutputMemoryView output_memory_view;
140
141  std::transform(output_memory.begin(), output_memory.end(),
142                 std::inserter(output_memory_view, output_memory_view.end()),
143                 [](model_runtime::OutputMemory::reference name_with_memory) {
144                   auto &&[name, memory] = name_with_memory;
145                   return std::make_pair(name, memory.getView());
146                 });
147
148  return output_memory_view;
149}
150
151inline void printInputMemory(const model_runtime::InputMemory &input_memory) {
152
153  using InputValueType =
154      std::pair<const std::string, model_runtime::TensorMemory>;
155  for (const InputValueType &name_with_memory : input_memory) {
156    auto &&[name, memory] = name_with_memory;
157    examples::print(
158        fmt::format("Input tensor {}, {} bytes", name, memory.data_size_bytes));
159  }
160}
161
162} // namespace examples

Download utils.hpp

9.2. Generating example PopEF file

Listing 9.2 generate_simple_popef.cpp
  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

Download generate_simple_popef.cpp