Home | History | Annotate | Download | only in service
      1 /* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
      2 
      3 Licensed under the Apache License, Version 2.0 (the "License");
      4 you may not use this file except in compliance with the License.
      5 You may obtain a copy of the License at
      6 
      7     http://www.apache.org/licenses/LICENSE-2.0
      8 
      9 Unless required by applicable law or agreed to in writing, software
     10 distributed under the License is distributed on an "AS IS" BASIS,
     11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 See the License for the specific language governing permissions and
     13 limitations under the License.
     14 ==============================================================================*/
     15 
     16 #include "tensorflow/compiler/xla/service/dump.h"
     17 #include "absl/strings/ascii.h"
     18 #include "tensorflow/compiler/xla/service/hlo_graph_dumper.h"
     19 #include "tensorflow/compiler/xla/service/hlo_module.h"
     20 #include "tensorflow/compiler/xla/service/hlo_proto_util.h"
     21 #include "tensorflow/compiler/xla/util.h"
     22 #include "tensorflow/core/lib/io/path.h"
     23 #include "tensorflow/core/lib/strings/proto_serialization.h"
     24 #include "tensorflow/core/platform/env.h"
     25 #include "tensorflow/core/platform/regexp.h"
     26 
     27 namespace xla {
     28 
     29 namespace {
     30 
     31 using absl::StrCat;
     32 using absl::StrFormat;
     33 using absl::string_view;
     34 
     35 struct CanonicalDebugOptions {
     36   explicit CanonicalDebugOptions(const DebugOptions& opts)
     37       : dump_to(opts.xla_dump_to()),
     38         dump_as_text(opts.xla_dump_hlo_as_text()),
     39         dump_as_proto(opts.xla_dump_hlo_as_proto()),
     40         dump_as_dot(opts.xla_dump_hlo_as_dot()),
     41         dump_as_html(opts.xla_dump_hlo_as_html()),
     42         dump_as_url(opts.xla_dump_hlo_as_url()),
     43         dump_snapshots(opts.xla_dump_hlo_snapshots()) {
     44     // This constructor examines the values in `opts` and turns on other flags
     45     // based on what we think is the user's intent.  To reduce confusion about
     46     // what was a user-specified value versus an extrapolated value, within this
     47     // function we treat this struct's members as write-only, and read only from
     48     // `opts`.
     49 
     50     // If dump_to is empty, default to dumping to stdout.
     51     if (opts.xla_dump_to().empty()) {
     52       dump_to = "-";
     53     }
     54 
     55     // Did the user specifiy an explicit format for dumping?
     56     bool output_format_specified =
     57         opts.xla_dump_hlo_as_text() || opts.xla_dump_hlo_as_proto() ||
     58         opts.xla_dump_hlo_as_dot() || opts.xla_dump_hlo_as_html() ||
     59         opts.xla_dump_hlo_as_url() || opts.xla_dump_hlo_snapshots();
     60 
     61     // If we haven't specified an output format, default to dumping as text.
     62     if (!output_format_specified) {
     63       dump_as_text = true;
     64     }
     65 
     66     // If we specified a regular expression restricting which modules to dump,
     67     // respect that.
     68     //
     69     // If we didn't specify which modules to dump but we passed some other flag
     70     // which implies dumping modules, dump all modules.
     71     //
     72     // Otherwise, don't dump any HLO modules.
     73     if (!opts.xla_dump_hlo_module_re().empty()) {
     74       // RE2 object is not copyable, and we can't capture "by move", so we
     75       // resort to this hack.
     76       string pattern = opts.xla_dump_hlo_module_re();
     77       should_dump_module = [pattern](string_view module_name) {
     78         return RE2::PartialMatch(string(module_name), pattern);
     79       };
     80     } else if (!opts.xla_dump_hlo_pass_re().empty() ||
     81                !opts.xla_dump_to().empty() || output_format_specified) {
     82       should_dump_module = [](string_view) { return true; };
     83     } else {
     84       should_dump_module = [](string_view) { return false; };
     85     }
     86 
     87     // Initialize should_dump_pass.  This one is easy: We only dump per-pass
     88     // data if the user asked for it explicitly.
     89     if (!opts.xla_dump_hlo_pass_re().empty()) {
     90       string pattern = opts.xla_dump_hlo_pass_re();
     91       should_dump_pass = [pattern](string_view pass_name) {
     92         return RE2::PartialMatch(string(pass_name), pattern);
     93       };
     94     } else {
     95       should_dump_pass = [](string_view) { return false; };
     96     }
     97 
     98     // Output dirs "sponge" and "test_undeclared_outputs_dir" (case-insensitive)
     99     // have a special meaning: Dump into the directory specified by the
    100     // environment variable TEST_UNDECLARED_OUTPUTS_DIR.
    101     string dump_to_lower = absl::AsciiStrToLower(opts.xla_dump_to());
    102     if (dump_to_lower == "sponge" ||
    103         dump_to_lower == "test_undeclared_outputs_dir") {
    104       const char* dir = getenv("TEST_UNDECLARED_OUTPUTS_DIR");
    105       if (dir != nullptr) {
    106         dump_to = dir;
    107       } else {
    108         LOG(ERROR) << "--xla_dump_to=" << opts.xla_dump_to()
    109                    << ", but environment variable TEST_UNDECLARED_OUTPUTS_DIR "
    110                       "is not set, so cannot dump anywhere.";
    111         should_dump_module = [](string_view) { return false; };
    112         should_dump_pass = [](string_view) { return false; };
    113       }
    114     }
    115   }
    116 
    117   bool dumping_to_stdout() const { return dump_to == "-"; }
    118 
    119   string dump_to;
    120   std::function<bool(string_view module_name)> should_dump_module;
    121   std::function<bool(string_view pass_name)> should_dump_pass;
    122 
    123   // dump_ir isn't present here because this file is mostly concerned with
    124   // dumping HLO.
    125   bool dump_as_text;
    126   bool dump_as_proto;
    127   bool dump_as_dot;
    128   bool dump_as_html;
    129   bool dump_as_url;
    130   bool dump_snapshots;
    131 };
    132 
    133 string FilenameFor(const HloModule& module, string_view suffix) {
    134   return StrFormat("module_%04d.%s", module.unique_id(), suffix);
    135 }
    136 
    137 void DumpToFileInDirImpl(string_view filename, string_view contents,
    138                          const CanonicalDebugOptions& opts) {
    139   if (opts.dumping_to_stdout()) {
    140     LOG(ERROR) << "Refusing to write " << filename
    141                << " to stdout.  Pass --xla_dump_to=<path> to write to a file.";
    142     return;
    143   }
    144 
    145   const string& dir = opts.dump_to;
    146   VLOG(1) << "Dumping " << filename << " to " << dir;
    147 
    148   tensorflow::Env* env = tensorflow::Env::Default();
    149   // Two threads can race to observe the absence of the dump directory and
    150   // simultaneously try to create it, causing the "losing" thread to get a
    151   // "directory already exists" error.  We can work around this by checking
    152   // again whether the dir exists.
    153   if (!env->IsDirectory(dir).ok()) {
    154     auto status = env->RecursivelyCreateDir(dir);
    155     if (!status.ok() && !env->IsDirectory(dir).ok()) {
    156       LOG(ERROR) << "Could not create directory " << dir
    157                  << " for dumping XLA debug data: " << status;
    158       return;
    159     }
    160   }
    161 
    162   string file_path =
    163       tensorflow::io::JoinPath(dir, SanitizeFileName(string(filename)));
    164   auto status = tensorflow::WriteStringToFile(env, file_path, contents);
    165   if (!status.ok()) {
    166     LOG(ERROR) << "Could not write XLA debug data to " << file_path << ": "
    167                << status;
    168   }
    169 }
    170 
    171 void DumpToFileInDirOrStdoutImpl(string_view filename, string_view contents,
    172                                  const CanonicalDebugOptions& opts) {
    173   // Dump to stdout if that's called for.
    174   if (opts.dumping_to_stdout()) {
    175     std::cout << "*** Begin " << filename << " ***\n"
    176               << contents << "\n*** End " << filename << " ***" << std::endl;
    177     return;
    178   }
    179 
    180   // Otherwise, dump to a file.
    181   DumpToFileInDirImpl(filename, contents, opts);
    182 }
    183 
    184 void DumpHloModuleImpl(const HloModule& module,
    185                        const BufferAssignment* buffer_assn,
    186                        const HloExecutionProfile* profile, string_view suffix,
    187                        const CanonicalDebugOptions& opts) {
    188   string filename = FilenameFor(module, suffix);
    189 
    190   if (opts.dump_as_text) {
    191     DumpToFileInDirOrStdoutImpl(StrCat(filename, ".txt"), module.ToString(),
    192                                 opts);
    193   }
    194 
    195   if (opts.dump_as_proto) {
    196     HloProto module_proto =
    197         buffer_assn ? MakeHloProto(module, *buffer_assn) : MakeHloProto(module);
    198     string pb;
    199     if (!tensorflow::SerializeToStringDeterministic(module_proto, &pb)) {
    200       pb = "Failed to serialize HLO module proto.";
    201     }
    202     DumpToFileInDirImpl(StrCat(filename, ".hlo.pb"), pb, opts);
    203   }
    204 
    205   auto render_graph = [&](RenderedGraphFormat format) {
    206     StatusOr<string> rendered_graph = RenderGraph(
    207         *module.entry_computation(),
    208         /*label=*/filename, module.config().debug_options(), format, profile);
    209     if (rendered_graph.ok()) {
    210       return std::move(rendered_graph).ValueOrDie();
    211     }
    212     return StrFormat("Error rendering graph: %s",
    213                      rendered_graph.status().ToString());
    214   };
    215 
    216   if (opts.dump_as_dot) {
    217     DumpToFileInDirImpl(StrFormat("%s.dot", filename),
    218                         render_graph(RenderedGraphFormat::kDot), opts);
    219   }
    220 
    221   if (opts.dump_as_html) {
    222     DumpToFileInDirImpl(StrFormat("%s.html", filename),
    223                         render_graph(RenderedGraphFormat::kHtml), opts);
    224   }
    225 
    226   // Special case for rendering graphs as URLs.  We'll dump them to a file
    227   // because why not, but we always log them to stdout as well.
    228   if (opts.dump_as_url) {
    229     string url = render_graph(RenderedGraphFormat::kUrl);
    230     std::cout << filename << " --> " << url << std::endl;
    231     if (!opts.dumping_to_stdout()) {
    232       DumpToFileInDirImpl(StrFormat("%s.url", filename), url, opts);
    233     }
    234   }
    235 }
    236 
    237 static tensorflow::mutex mu(tensorflow::LINKER_INITIALIZED);
    238 
    239 // Maps a module's unique ID to a counter indicating how many times we've dumped
    240 // this module during the compilation pipeline.  This lets us keep the filenames
    241 // ordered nicely.
    242 //
    243 // Entries added here leak forever; we have no way to GC them when a module
    244 // dies.  But we only add an entry if dumping is enabled for this module, and
    245 // dumping a module leaks buffer space in stdout or bytes on disk *way* faster
    246 // than this hashtable leaks memory.
    247 static auto& module_id_to_step_number GUARDED_BY(mu) =
    248     *new absl::flat_hash_map<int64, int64>();
    249 
    250 }  // namespace
    251 
    252 void DumpToFileInDir(const HloModule& module, string_view suffix,
    253                      string_view contents) {
    254   DumpToFileInDirImpl(FilenameFor(module, suffix), contents,
    255                       CanonicalDebugOptions(module.config().debug_options()));
    256 }
    257 
    258 void DumpToFileInDirOrStdout(const HloModule& module, string_view suffix,
    259                              string_view contents) {
    260   DumpToFileInDirOrStdoutImpl(
    261       FilenameFor(module, suffix), contents,
    262       CanonicalDebugOptions(module.config().debug_options()));
    263 }
    264 
    265 void DumpHloModuleIfEnabled(const HloModule& module, string_view name) {
    266   CanonicalDebugOptions opts(module.config().debug_options());
    267   if (opts.should_dump_module(module.name())) {
    268     DumpHloModuleImpl(module, /*buffer_assn=*/nullptr, /*profile=*/nullptr,
    269                       name, opts);
    270   }
    271 }
    272 void DumpHloModuleIfEnabled(const HloModule& module,
    273                             const BufferAssignment& buffer_assn,
    274                             string_view name) {
    275   CanonicalDebugOptions opts(module.config().debug_options());
    276   if (opts.should_dump_module(module.name())) {
    277     DumpHloModuleImpl(module, &buffer_assn, /*profile=*/nullptr, name, opts);
    278   }
    279 }
    280 
    281 void DumpHloModuleIfEnabled(const HloModule& module,
    282                             const HloExecutionProfile& profile,
    283                             string_view name) {
    284   CanonicalDebugOptions opts(module.config().debug_options());
    285   if (opts.should_dump_module(module.name())) {
    286     DumpHloModuleImpl(module, /*buffer_assn=*/nullptr, &profile, name, opts);
    287   }
    288 }
    289 
    290 bool DumpingEnabledForHloModule(string_view hlo_module_name,
    291                                 const DebugOptions& opts) {
    292   return CanonicalDebugOptions(opts).should_dump_module(hlo_module_name);
    293 }
    294 
    295 bool DumpingToStdout(const DebugOptions& opts) {
    296   return CanonicalDebugOptions(opts).dumping_to_stdout();
    297 }
    298 
    299 void DumpHloModuleBetweenPassesIfEnabled(string_view pipeline_name,
    300                                          string_view before_pass_name,
    301                                          string_view after_pass_name,
    302                                          const HloModule& module) {
    303   CanonicalDebugOptions opts(module.config().debug_options());
    304   if (!opts.should_dump_module(module.name())) {
    305     return;
    306   }
    307 
    308   if (!opts.should_dump_pass(before_pass_name) &&
    309       !opts.should_dump_pass(after_pass_name)) {
    310     return;
    311   }
    312 
    313   int64 step_number;
    314   {
    315     tensorflow::mutex_lock lock(mu);
    316     step_number = module_id_to_step_number[module.unique_id()]++;
    317   }
    318 
    319   string filename_suffix =
    320       StrFormat("%04d.%s.after_%s.before_%s", step_number, pipeline_name,
    321                 after_pass_name, before_pass_name);
    322   DumpHloModuleImpl(module, /*buffer_assn=*/nullptr, /*profile=*/nullptr,
    323                     filename_suffix, opts);
    324 }
    325 
    326 void DumpHloModuleDuringPassIfEnabled(string_view pass_name,
    327                                       string_view step_name,
    328                                       const HloModule& module) {
    329   CanonicalDebugOptions opts(module.config().debug_options());
    330   if (!opts.should_dump_module(module.name()) ||
    331       !opts.should_dump_pass(pass_name)) {
    332     return;
    333   }
    334 
    335   int64 step_number;
    336   {
    337     tensorflow::mutex_lock lock(mu);
    338     step_number = module_id_to_step_number[module.unique_id()]++;
    339   }
    340 
    341   string filename_suffix =
    342       StrFormat("%04d.%s.%s", step_number, pass_name, step_name);
    343   DumpHloModuleImpl(module, /*buffer_assn=*/nullptr, /*profile=*/nullptr,
    344                     filename_suffix, opts);
    345 }
    346 
    347 void DumpHloSnapshotIfEnabled(const HloModule& module,
    348                               const HloSnapshot& snapshot) {
    349   CanonicalDebugOptions opts(module.config().debug_options());
    350   if (!opts.should_dump_module(module.name()) || !opts.dump_snapshots) {
    351     return;
    352   }
    353   int64 execution_count;
    354   {
    355     static auto& module_id_to_execution_count GUARDED_BY(mu) =
    356         *new absl::flat_hash_map<int64, int64>();
    357     tensorflow::mutex_lock lock(mu);
    358     execution_count = module_id_to_execution_count[module.unique_id()]++;
    359   }
    360   string filename =
    361       StrCat(FilenameFor(module, StrFormat("execution_%04d", execution_count)),
    362              ".hlo_snapshot.pb");
    363   if (opts.dumping_to_stdout()) {
    364     LOG(ERROR) << "Refusing to write HLO snapshot proto for " << filename
    365                << " to stdout.  Pass --xla_dump_to=<path> to write to a file.";
    366     return;
    367   }
    368   string pb;
    369   if (!tensorflow::SerializeToStringDeterministic(snapshot, &pb)) {
    370     LOG(ERROR) << "Failed to serialize HLO snapshot proto " << filename;
    371   }
    372   DumpToFileInDirImpl(filename, pb, opts);
    373 }
    374 
    375 void DumpHloSnapshotIfEnabled(const HloSnapshot& snapshot,
    376                               const DebugOptions& opts) {
    377   CanonicalDebugOptions canonical_opts(opts);
    378   string name = snapshot.hlo().hlo_module().name();
    379   if (!canonical_opts.should_dump_module(name) ||
    380       !canonical_opts.dump_snapshots) {
    381     return;
    382   }
    383 
    384   // We don't have a unique id for an HloSnapshot, so in this overload we just
    385   // have to use its name.
    386   int64 execution_count;
    387   {
    388     static auto& module_name_to_execution_count GUARDED_BY(mu) =
    389         *new absl::flat_hash_map<string, int64>();
    390     tensorflow::mutex_lock lock(mu);
    391     execution_count = module_name_to_execution_count[name]++;
    392   }
    393   string filename = StrFormat("module_%s.execution_%04d.hlo_snapshot.pb", name,
    394                               execution_count);
    395   if (canonical_opts.dumping_to_stdout()) {
    396     LOG(ERROR) << "Refusing to write HLO snapshot proto for " << filename
    397                << " to stdout.  Pass --xla_dump_to=<path> to write to a file.";
    398     return;
    399   }
    400   string pb;
    401   if (!tensorflow::SerializeToStringDeterministic(snapshot, &pb)) {
    402     LOG(ERROR) << "Failed to serialize HLO snapshot proto " << filename;
    403   }
    404   DumpToFileInDirImpl(filename, pb, canonical_opts);
    405 }
    406 
    407 }  // namespace xla
    408