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
18 #include "absl/strings/ascii.h"
19 #include "absl/strings/str_cat.h"
20 #include "tensorflow/compiler/xla/service/hlo_graph_dumper.h"
21 #include "tensorflow/compiler/xla/service/hlo_module.h"
22 #include "tensorflow/compiler/xla/service/hlo_proto_util.h"
23 #include "tensorflow/compiler/xla/util.h"
24 #include "tensorflow/core/lib/core/status.h"
25 #include "tensorflow/core/lib/io/path.h"
26 #include "tensorflow/core/lib/io/zlib_compression_options.h"
27 #include "tensorflow/core/lib/io/zlib_outputbuffer.h"
28 #include "tensorflow/core/lib/strings/proto_serialization.h"
29 #include "tensorflow/core/platform/env.h"
30 #include "tensorflow/core/platform/path.h"
31 #include "tensorflow/core/platform/regexp.h"
32
33 namespace xla {
34
35 namespace {
36
37 using absl::StrCat;
38 using absl::StrFormat;
39 using absl::string_view;
40
41 struct CanonicalDebugOptions {
CanonicalDebugOptionsxla::__anon28f990a90111::CanonicalDebugOptions42 explicit CanonicalDebugOptions(const DebugOptions& opts)
43 : dump_to(opts.xla_dump_to()),
44 dump_as_text(opts.xla_dump_hlo_as_text()),
45 dump_as_proto(opts.xla_dump_hlo_as_proto()),
46 dump_as_dot(opts.xla_dump_hlo_as_dot()),
47 dump_as_html(opts.xla_dump_hlo_as_html()),
48 dump_as_url(opts.xla_dump_hlo_as_url()),
49 dump_fusion_visualization(opts.xla_dump_fusion_visualization()),
50 dump_snapshots(opts.xla_dump_hlo_snapshots()),
51 dump_include_timestamp(opts.xla_dump_include_timestamp()),
52 dump_max_hlo_modules(opts.xla_dump_max_hlo_modules()),
53 dump_module_metadata(opts.xla_dump_module_metadata()),
54 dump_compress_protos(opts.xla_dump_compress_protos()),
55 dump_hlo_metadata(!opts.xla_dump_disable_metadata()) {
56 // This constructor examines the values in `opts` and turns on other flags
57 // based on what we think is the user's intent. To reduce confusion about
58 // what was a user-specified value versus an extrapolated value, within this
59 // function we treat this struct's members as write-only, and read only from
60 // `opts`.
61
62 // Did the user specify an explicit format for dumping?
63 bool output_format_other_than_url_specified =
64 opts.xla_dump_hlo_as_text() || opts.xla_dump_hlo_as_proto() ||
65 opts.xla_dump_hlo_as_dot() || opts.xla_dump_hlo_as_html() ||
66 opts.xla_dump_hlo_snapshots();
67 bool output_format_specified =
68 output_format_other_than_url_specified || opts.xla_dump_hlo_as_url();
69
70 // If we haven't specified an output format, default to dumping as text.
71 if (!output_format_specified) {
72 dump_as_text = true;
73 }
74
75 // Disable dumping if specified by the user.
76 if (!opts.xla_detailed_logging_and_dumping()) {
77 dump_to = "";
78 }
79
80 // If dump_to is empty, default to dumping to stdout, so long as some dump
81 // format other than dump-as-url was specified. If the user only specified
82 // --xla_dump_hlo_as_url, then don't dump to stdout, that is likely noise
83 // they don't want.
84 if (opts.xla_dump_to().empty() && output_format_other_than_url_specified) {
85 dump_to = "-";
86 }
87
88 // If we specified a regular expression restricting which modules to dump,
89 // respect that.
90 //
91 // If we didn't specify which modules to dump but we passed some other flag
92 // which implies dumping modules, dump all modules.
93 //
94 // Otherwise, don't dump any HLO modules.
95 if (!opts.xla_dump_hlo_module_re().empty()) {
96 // RE2 object is not copyable, and we can't capture "by move", so we
97 // resort to this hack.
98 string pattern = opts.xla_dump_hlo_module_re();
99 should_dump_module = [pattern](string_view module_name) {
100 return RE2::PartialMatch(module_name, pattern);
101 };
102 } else if (!opts.xla_dump_hlo_pass_re().empty() ||
103 !opts.xla_dump_to().empty() || output_format_specified) {
104 should_dump_module = [](string_view) { return true; };
105 } else {
106 should_dump_module = [](string_view) { return false; };
107 }
108
109 // Initialize should_dump_pass. This one is easy: We only dump per-pass
110 // data if the user asked for it explicitly.
111 if (!opts.xla_dump_hlo_pass_re().empty()) {
112 string pattern = opts.xla_dump_hlo_pass_re();
113 should_dump_pass = [pattern](string_view pass_name) {
114 return RE2::PartialMatch(pass_name, pattern);
115 };
116 } else {
117 should_dump_pass = [](string_view) { return false; };
118 }
119
120 // Initialize should_dump_pipeline. If the option was not specified, dump
121 // all pipelines. Otherwise dump only those pipelines that user asked for
122 // explicitly.
123 if (!opts.xla_dump_hlo_pipeline_re().empty()) {
124 string pattern = opts.xla_dump_hlo_pipeline_re();
125 should_dump_pipeline = [pattern](string_view pipeline_name) {
126 return RE2::PartialMatch(pipeline_name, pattern);
127 };
128 } else {
129 should_dump_pipeline = [](string_view) { return true; };
130 }
131
132 // Output dirs "sponge" and "test_undeclared_outputs_dir" (case-insensitive)
133 // have a special meaning: Dump into the directory specified by the
134 // environment variable TEST_UNDECLARED_OUTPUTS_DIR.
135 string dump_to_lower = absl::AsciiStrToLower(dump_to);
136 if (dump_to_lower == "sponge" ||
137 dump_to_lower == "test_undeclared_outputs_dir") {
138 if (!tensorflow::io::GetTestUndeclaredOutputsDir(&dump_to)) {
139 LOG(ERROR) << "--xla_dump_to=" << opts.xla_dump_to()
140 << ", but environment variable TEST_UNDECLARED_OUTPUTS_DIR "
141 "is not set, so cannot dump anywhere.";
142 should_dump_module = [](string_view) { return false; };
143 should_dump_pass = [](string_view) { return false; };
144 should_dump_pipeline = [](string_view) { return false; };
145 }
146 }
147 }
148
dumping_to_stdoutxla::__anon28f990a90111::CanonicalDebugOptions149 bool dumping_to_stdout() const { return dump_to == "-"; }
150
151 string dump_to;
152 std::function<bool(string_view module_name)> should_dump_module;
153 std::function<bool(string_view pass_name)> should_dump_pass;
154 std::function<bool(string_view pipeline_name)> should_dump_pipeline;
155
156 // dump_ir isn't present here because this file is mostly concerned with
157 // dumping HLO.
158 bool dump_as_text;
159 bool dump_as_proto;
160 bool dump_as_dot;
161 bool dump_as_html;
162 bool dump_as_url;
163 bool dump_fusion_visualization;
164 bool dump_snapshots;
165 bool dump_include_timestamp;
166 int64 dump_max_hlo_modules;
167 bool dump_module_metadata;
168 bool dump_compress_protos;
169 bool dump_hlo_metadata;
170 };
171
WriteStringToFile(tensorflow::Env * env,const string & fname,const tensorflow::StringPiece & data,bool compressed)172 Status WriteStringToFile(tensorflow::Env* env, const string& fname,
173 const tensorflow::StringPiece& data, bool compressed) {
174 if (!compressed) {
175 return tensorflow::WriteStringToFile(env, fname, data);
176 }
177 std::unique_ptr<tensorflow::WritableFile> file;
178 TF_RETURN_IF_ERROR(env->NewWritableFile(fname, &file));
179 auto gz_opts = tensorflow::io::ZlibCompressionOptions::GZIP();
180 tensorflow::io::ZlibOutputBuffer gz_file(file.get(),
181 gz_opts.input_buffer_size,
182 gz_opts.output_buffer_size, gz_opts);
183 TF_RETURN_IF_ERROR(gz_file.Init());
184 TF_RETURN_IF_ERROR(gz_file.Append(data));
185 return gz_file.Close();
186 }
187
DumpToFileInDirImpl(string_view filename,string_view contents,const CanonicalDebugOptions & opts,bool compress=false)188 absl::optional<std::string> DumpToFileInDirImpl(
189 string_view filename, string_view contents,
190 const CanonicalDebugOptions& opts, bool compress = false) {
191 if (opts.dumping_to_stdout()) {
192 LOG(ERROR) << "Refusing to write " << filename
193 << " to stdout. Pass --xla_dump_to=<path> to write to a file.";
194 return absl::nullopt;
195 }
196
197 if (opts.dump_to.empty()) {
198 return absl::nullopt;
199 }
200
201 const string& dir = opts.dump_to;
202 VLOG(1) << "Dumping " << filename << " to " << dir;
203
204 tensorflow::Env* env = tensorflow::Env::Default();
205 // Two threads can race to observe the absence of the dump directory and
206 // simultaneously try to create it, causing the "losing" thread to get a
207 // "directory already exists" error. We can work around this by checking
208 // again whether the dir exists.
209 if (!env->IsDirectory(dir).ok()) {
210 auto status = env->RecursivelyCreateDir(dir);
211 if (!status.ok() && !env->IsDirectory(dir).ok()) {
212 LOG(ERROR) << "Could not create directory " << dir
213 << " for dumping XLA debug data: " << status;
214 return absl::nullopt;
215 }
216 }
217
218 // Make sure we are not going to dump more modules than the user has asked.
219 if (opts.dump_max_hlo_modules > 0) {
220 std::vector<string> matches;
221 auto pattern = tensorflow::io::JoinPath(dir, "*module_*.*");
222 auto status = env->GetMatchingPaths(pattern, &matches);
223 if (!status.ok()) {
224 LOG(ERROR) << "Could not get matching paths for pattern " << pattern
225 << ": " << status;
226 }
227 static const LazyRE2 module_id_regex = {R"(.*module_(\d+)\..*)"};
228 absl::flat_hash_set<int64> dumped_module_ids;
229 for (const string& match : matches) {
230 int64_t dumped_module_id;
231 if (RE2::FullMatch(match, *module_id_regex, &dumped_module_id)) {
232 dumped_module_ids.insert(dumped_module_id);
233 }
234 }
235 if (dumped_module_ids.size() >= opts.dump_max_hlo_modules) {
236 int64_t module_id;
237 if (RE2::FullMatch(filename, *module_id_regex, &module_id) &&
238 !dumped_module_ids.contains(module_id)) {
239 LOG(ERROR) << "Have already dumped " << dumped_module_ids.size()
240 << " modules, more than the limit of "
241 << opts.dump_max_hlo_modules;
242 return absl::nullopt;
243 }
244 }
245 }
246
247 string file_path =
248 tensorflow::io::JoinPath(dir, SanitizeFileName(string(filename)));
249 auto status = WriteStringToFile(env, file_path, contents, compress);
250 if (!status.ok()) {
251 LOG(ERROR) << "Could not write XLA debug data to " << file_path << ": "
252 << status;
253 }
254
255 return file_path;
256 }
257
DumpToFileInDirOrStdoutImpl(string_view filename,string_view contents,const CanonicalDebugOptions & opts)258 absl::optional<std::string> DumpToFileInDirOrStdoutImpl(
259 string_view filename, string_view contents,
260 const CanonicalDebugOptions& opts) {
261 // Dump to stdout if that's called for.
262 if (opts.dumping_to_stdout()) {
263 std::cout << "*** Begin " << filename << " ***\n"
264 << contents << "\n*** End " << filename << " ***" << std::endl;
265 return absl::nullopt;
266 }
267
268 // Otherwise, dump to a file.
269 return DumpToFileInDirImpl(filename, contents, opts);
270 }
271
272 // Returns full file paths of all dumps of the module.
DumpHloModuleImpl(const HloModule & module,const BufferAssignment * buffer_assn,const HloExecutionProfile * profile,string_view prefix,string_view suffix,const CanonicalDebugOptions & opts)273 std::vector<std::string> DumpHloModuleImpl(const HloModule& module,
274 const BufferAssignment* buffer_assn,
275 const HloExecutionProfile* profile,
276 string_view prefix,
277 string_view suffix,
278 const CanonicalDebugOptions& opts) {
279 string filename = FilenameFor(module, prefix, suffix);
280
281 std::vector<absl::optional<std::string>> file_paths;
282
283 if (opts.dump_as_text) {
284 HloPrintOptions print_options;
285 print_options.set_print_backend_config(true);
286 print_options.set_print_metadata(opts.dump_hlo_metadata);
287 file_paths.push_back(DumpToFileInDirOrStdoutImpl(
288 StrCat(filename, ".txt"), module.ToString(print_options), opts));
289 if (buffer_assn) {
290 file_paths.push_back(DumpToFileInDirOrStdoutImpl(
291 StrCat(filename, "-buffer-assignment.txt"),
292 StrCat(buffer_assn->ToString(), "\n\n",
293 buffer_assn->hlo_live_range().ToString()),
294 opts));
295 }
296 }
297
298 if (opts.dump_as_proto) {
299 HloProto module_proto =
300 buffer_assn ? MakeHloProto(module, *buffer_assn) : MakeHloProto(module);
301 string pb;
302 if (!tensorflow::SerializeToStringDeterministic(module_proto, &pb)) {
303 pb = "Failed to serialize HLO module proto.";
304 }
305 file_paths.push_back(DumpToFileInDirImpl(
306 StrCat(filename, opts.dump_compress_protos ? ".hlo.pb.gz" : ".hlo.pb"),
307 pb, opts, opts.dump_compress_protos));
308 }
309
310 auto render_graph = [&](RenderedGraphFormat format) {
311 StatusOr<string> rendered_graph = RenderGraph(
312 *module.entry_computation(),
313 /*label=*/filename, module.config().debug_options(), format, profile);
314 if (rendered_graph.ok()) {
315 return std::move(rendered_graph).ValueOrDie();
316 }
317 return StrFormat("Error rendering graph: %s",
318 rendered_graph.status().ToString());
319 };
320
321 if (opts.dump_as_dot) {
322 file_paths.push_back(
323 DumpToFileInDirImpl(StrFormat("%s.dot", filename),
324 render_graph(RenderedGraphFormat::kDot), opts));
325 }
326
327 if (opts.dump_as_html) {
328 file_paths.push_back(
329 DumpToFileInDirImpl(StrFormat("%s.html", filename),
330 render_graph(RenderedGraphFormat::kHtml), opts));
331 }
332
333 if (opts.dump_fusion_visualization) {
334 for (const HloComputation* computation :
335 module.MakeNonfusionComputations()) {
336 StatusOr<string> rendered_graph = RenderGraph(
337 *computation,
338 /*label=*/absl::StrCat(filename, "_", computation->name()),
339 module.config().debug_options(),
340 RenderedGraphFormat::kFusionVisualization, profile);
341 file_paths.push_back(DumpToFileInDirImpl(
342 StrFormat("%s_%s_fusion_visualization.html", filename,
343 computation->name()),
344 rendered_graph.ok() ? *rendered_graph
345 : StrFormat("Error rendering graph: %s",
346 rendered_graph.status().ToString()),
347 opts));
348 }
349 }
350
351 // Special case for rendering graphs as URLs. We'll dump them to a file
352 // because why not, but we always log them to stdout as well.
353 if (opts.dump_as_url) {
354 string url = render_graph(RenderedGraphFormat::kUrl);
355 std::cout << filename << " --> " << url << std::endl;
356 if (!opts.dumping_to_stdout()) {
357 file_paths.push_back(
358 DumpToFileInDirImpl(StrFormat("%s.url", filename), url, opts));
359 }
360 }
361
362 std::vector<std::string> dumped_file_paths;
363 for (const absl::optional<std::string>& path : file_paths) {
364 if (path.has_value()) {
365 dumped_file_paths.push_back(*path);
366 }
367 }
368 return dumped_file_paths;
369 }
370
DumpHloModuleMetadata(const HloModuleMetadataProto & metadata,const CanonicalDebugOptions & opts,absl::flat_hash_set<int64> * dumped_module_ids)371 void DumpHloModuleMetadata(const HloModuleMetadataProto& metadata,
372 const CanonicalDebugOptions& opts,
373 absl::flat_hash_set<int64>* dumped_module_ids) {
374 // Return if metadata for this module has already been dumped.
375 if (!dumped_module_ids->insert(metadata.canonical_module_id()).second) {
376 return;
377 }
378 std::string filename = absl::StrFormat("module_%04d.metadata.textproto",
379 metadata.canonical_module_id());
380 std::string content;
381 if (tensorflow::protobuf::TextFormat::PrintToString(metadata, &content)) {
382 DumpToFileInDirImpl(filename, content, opts);
383 } else {
384 LOG(ERROR) << "Failed to convert HloModuleMetadataProto to text.";
385 }
386 }
387
388 static tensorflow::mutex mu(tensorflow::LINKER_INITIALIZED);
389
390 // Maps a module's unique ID to a counter indicating how many times we've dumped
391 // this module during the compilation pipeline. This lets us keep the filenames
392 // ordered nicely.
393 //
394 // Entries added here leak forever; we have no way to GC them when a module
395 // dies. But we only add an entry if dumping is enabled for this module, and
396 // dumping a module leaks buffer space in stdout or bytes on disk *way* faster
397 // than this hashtable leaks memory.
398 static auto& module_id_to_step_number TF_GUARDED_BY(mu) =
399 *new absl::flat_hash_map<int64, int64>();
400
401 // Maps a module's unique ID to a timestamp indicating when we've first dumped
402 // this module during the compilation pipeline and when we first started
403 // compiling this module. This lets us keep the filenames ordered nicely.
404 //
405 // Entries added here leak forever; we have no way to GC them when a module
406 // dies. But we only add an entry if dumping is enabled for this module, and
407 // dumping a module leaks buffer space in stdout or bytes on disk *way* faster
408 // than this hashtable leaks memory.
409 static auto& module_id_to_timestamp TF_GUARDED_BY(mu) =
410 *new absl::flat_hash_map<int64, uint64>();
411
StepNumberForModule(const HloModule & module)412 int64 StepNumberForModule(const HloModule& module) {
413 tensorflow::mutex_lock lock(mu);
414 return module_id_to_step_number[module.unique_id()]++;
415 }
416
417 } // namespace
418
419 // Get a timestamp which we can use as a filename prefix specific to this
420 // module.
TimestampFor(const HloModule & module)421 string TimestampFor(const HloModule& module) {
422 if (!module.config().debug_options().xla_dump_include_timestamp()) {
423 return "";
424 }
425 tensorflow::mutex_lock lock(mu);
426 auto timestamp_emplace = module_id_to_timestamp.try_emplace(
427 module.unique_id(), tensorflow::Env::Default()->NowMicros());
428 return std::to_string(timestamp_emplace.first->second);
429 }
430
FilenameFor(int unique_id,string_view module_name,string_view prefix,string_view suffix)431 static string FilenameFor(int unique_id, string_view module_name,
432 string_view prefix, string_view suffix) {
433 string filename;
434 if (!prefix.empty()) {
435 absl::StrAppend(&filename, prefix, ".");
436 }
437 absl::StrAppendFormat(&filename, "module_%04d", unique_id);
438 if (!module_name.empty()) {
439 absl::StrAppend(&filename, ".", module_name);
440 }
441 absl::StrAppend(&filename, ".", suffix);
442 // Skip the module name if the resulting length is too long.
443 if (!module_name.empty() && filename.size() > 255) {
444 return FilenameFor(unique_id, "", prefix, suffix);
445 }
446 return filename;
447 }
448
FilenameFor(const HloModule & module,string_view prefix,string_view suffix)449 string FilenameFor(const HloModule& module, string_view prefix,
450 string_view suffix) {
451 return FilenameFor(module.unique_id(), module.name(), prefix, suffix);
452 }
453
DumpToFileInDir(const HloModule & module,string_view file_prefix,string_view file_suffix,string_view contents)454 void DumpToFileInDir(const HloModule& module, string_view file_prefix,
455 string_view file_suffix, string_view contents) {
456 DumpToFileInDirImpl(FilenameFor(module, file_prefix, file_suffix), contents,
457 CanonicalDebugOptions(module.config().debug_options()));
458 }
459
DumpToFileInDirOrStdout(const HloModule & module,string_view file_prefix,string_view file_suffix,string_view contents)460 void DumpToFileInDirOrStdout(const HloModule& module, string_view file_prefix,
461 string_view file_suffix, string_view contents) {
462 DumpToFileInDirOrStdoutImpl(
463 FilenameFor(module, file_prefix, file_suffix), contents,
464 CanonicalDebugOptions(module.config().debug_options()));
465 }
466
DumpToFileInDirOrStdout(const DebugOptions & debug_options,int unique_id,string_view module_name,string_view file_prefix,string_view file_suffix,string_view contents)467 void DumpToFileInDirOrStdout(const DebugOptions& debug_options, int unique_id,
468 string_view module_name, string_view file_prefix,
469 string_view file_suffix, string_view contents) {
470 DumpToFileInDirOrStdoutImpl(
471 FilenameFor(unique_id, module_name, file_prefix, file_suffix), contents,
472 CanonicalDebugOptions(debug_options));
473 }
474
DumpExecutionOptions(const ExecutionOptions & execution_options,const DebugOptions & debug_options)475 void DumpExecutionOptions(const ExecutionOptions& execution_options,
476 const DebugOptions& debug_options) {
477 CanonicalDebugOptions opts(debug_options);
478 tensorflow::Env* env = tensorflow::Env::Default();
479 const string& dir = opts.dump_to;
480 if (!env->IsDirectory(dir).ok()) {
481 auto status = env->RecursivelyCreateDir(dir);
482 if (!status.ok()) {
483 LOG(ERROR) << "Could not create directory " << dir
484 << " for dumping XLA execution options: " << status;
485 return;
486 }
487 }
488 if (env->IsDirectory(dir).ok()) {
489 string filename = tensorflow::io::JoinPath(dir, "execution_options");
490 Status status;
491 if (opts.dump_as_text) {
492 status = tensorflow::WriteTextProto(env, absl::StrCat(filename, ".txt"),
493 execution_options);
494 } else {
495 status = tensorflow::WriteBinaryProto(env, absl::StrCat(filename, ".pb"),
496 execution_options);
497 }
498 if (!status.ok()) {
499 LOG(ERROR) << "Could not write XLA debug data to " << filename << ": "
500 << status;
501 }
502 }
503 }
504
DumpHloModuleIfEnabled(const HloModule & module,string_view name)505 void DumpHloModuleIfEnabled(const HloModule& module, string_view name) {
506 CanonicalDebugOptions opts(module.config().debug_options());
507 if (opts.should_dump_module(module.name())) {
508 DumpHloModuleImpl(module, /*buffer_assn=*/nullptr, /*profile=*/nullptr,
509 TimestampFor(module), name, opts);
510 }
511 }
512
DumpHloModuleIfEnabled(const HloModule & module,const BufferAssignment & buffer_assn,string_view name)513 void DumpHloModuleIfEnabled(const HloModule& module,
514 const BufferAssignment& buffer_assn,
515 string_view name) {
516 CanonicalDebugOptions opts(module.config().debug_options());
517 if (opts.should_dump_module(module.name())) {
518 DumpHloModuleImpl(module, &buffer_assn, /*profile=*/nullptr,
519 TimestampFor(module), name, opts);
520 }
521 }
522
DumpHloModuleIfEnabled(const HloModule & module,const HloExecutionProfile & profile,string_view name)523 void DumpHloModuleIfEnabled(const HloModule& module,
524 const HloExecutionProfile& profile,
525 string_view name) {
526 CanonicalDebugOptions opts(module.config().debug_options());
527 if (opts.should_dump_module(module.name())) {
528 DumpHloModuleImpl(module, /*buffer_assn=*/nullptr, &profile,
529 TimestampFor(module), name, opts);
530 }
531 }
532
DumpingEnabledForHloModule(string_view hlo_module_name,const DebugOptions & opts)533 bool DumpingEnabledForHloModule(string_view hlo_module_name,
534 const DebugOptions& opts) {
535 return CanonicalDebugOptions(opts).should_dump_module(hlo_module_name);
536 }
537
DumpingToStdout(const DebugOptions & opts)538 bool DumpingToStdout(const DebugOptions& opts) {
539 return CanonicalDebugOptions(opts).dumping_to_stdout();
540 }
541
DumpHloModuleBetweenPassesIfEnabled(string_view pipeline_name,string_view before_pass_name,string_view after_pass_name,const HloModule & module)542 std::vector<std::string> DumpHloModuleBetweenPassesIfEnabled(
543 string_view pipeline_name, string_view before_pass_name,
544 string_view after_pass_name, const HloModule& module) {
545 CanonicalDebugOptions opts(module.config().debug_options());
546 if (!opts.should_dump_module(module.name())) {
547 return {};
548 }
549
550 if (!opts.should_dump_pass(before_pass_name) &&
551 !opts.should_dump_pass(after_pass_name)) {
552 return {};
553 }
554
555 if (!opts.should_dump_pipeline(pipeline_name)) {
556 return {};
557 }
558
559 int64_t step_number = StepNumberForModule(module);
560 std::string timestamp = TimestampFor(module);
561
562 string filename_suffix =
563 StrFormat("%04d.%s.after_%s.before_%s", step_number, pipeline_name,
564 after_pass_name, before_pass_name);
565 return DumpHloModuleImpl(module, /*buffer_assn=*/nullptr, /*profile=*/nullptr,
566 timestamp, filename_suffix, opts);
567 }
568
DumpHloModuleDuringPassIfEnabled(string_view pass_name,string_view step_name,const HloModule & module)569 void DumpHloModuleDuringPassIfEnabled(string_view pass_name,
570 string_view step_name,
571 const HloModule& module) {
572 CanonicalDebugOptions opts(module.config().debug_options());
573 if (!opts.should_dump_module(module.name()) ||
574 !opts.should_dump_pass(pass_name)) {
575 return;
576 }
577
578 int64_t step_number = StepNumberForModule(module);
579 std::string timestamp = TimestampFor(module);
580
581 string filename_suffix =
582 StrFormat("%04d.%s.%s", step_number, pass_name, step_name);
583 DumpHloModuleImpl(module, /*buffer_assn=*/nullptr, /*profile=*/nullptr,
584 timestamp, filename_suffix, opts);
585 }
586
DumpHloSnapshotIfEnabled(const HloModule & module,const HloSnapshot & snapshot)587 void DumpHloSnapshotIfEnabled(const HloModule& module,
588 const HloSnapshot& snapshot) {
589 CanonicalDebugOptions opts(module.config().debug_options());
590 if (!opts.should_dump_module(module.name()) || !opts.dump_snapshots) {
591 return;
592 }
593 int64_t execution_count;
594 uint64 timestamp;
595 {
596 static auto& module_id_to_execution_count TF_GUARDED_BY(mu) =
597 *new absl::flat_hash_map<int64, int64>();
598 tensorflow::mutex_lock lock(mu);
599 execution_count = module_id_to_execution_count[module.unique_id()]++;
600 auto timestamp_emplace = module_id_to_timestamp.try_emplace(
601 module.unique_id(), tensorflow::Env::Default()->NowMicros());
602 timestamp = timestamp_emplace.first->second;
603 }
604 string filename =
605 StrCat(FilenameFor(module, std::to_string(timestamp),
606 StrFormat("execution_%04d", execution_count)),
607 ".hlo_snapshot.pb");
608 if (opts.dumping_to_stdout()) {
609 LOG(ERROR) << "Refusing to write HLO snapshot proto for " << filename
610 << " to stdout. Pass --xla_dump_to=<path> to write to a file.";
611 return;
612 }
613 string pb;
614 if (!tensorflow::SerializeToStringDeterministic(snapshot, &pb)) {
615 LOG(ERROR) << "Failed to serialize HLO snapshot proto " << filename;
616 }
617 DumpToFileInDirImpl(filename, pb, opts);
618 }
619
DumpHloSnapshotIfEnabled(const HloSnapshot & snapshot,const DebugOptions & opts)620 void DumpHloSnapshotIfEnabled(const HloSnapshot& snapshot,
621 const DebugOptions& opts) {
622 CanonicalDebugOptions canonical_opts(opts);
623 string name = snapshot.hlo().hlo_module().name();
624 if (!canonical_opts.should_dump_module(name) ||
625 !canonical_opts.dump_snapshots) {
626 return;
627 }
628
629 // We don't have a unique id for an HloSnapshot, so in this overload we just
630 // have to use its name.
631 int64_t execution_count;
632 {
633 static auto& module_name_to_execution_count TF_GUARDED_BY(mu) =
634 *new absl::flat_hash_map<string, int64>();
635 tensorflow::mutex_lock lock(mu);
636 execution_count = module_name_to_execution_count[name]++;
637 }
638 string filename = StrFormat("module_%s.execution_%04d.hlo_snapshot.pb", name,
639 execution_count);
640 if (canonical_opts.dumping_to_stdout()) {
641 LOG(ERROR) << "Refusing to write HLO snapshot proto for " << filename
642 << " to stdout. Pass --xla_dump_to=<path> to write to a file.";
643 return;
644 }
645 string pb;
646 if (!tensorflow::SerializeToStringDeterministic(snapshot, &pb)) {
647 LOG(ERROR) << "Failed to serialize HLO snapshot proto " << filename;
648 }
649 DumpToFileInDirImpl(filename, pb, canonical_opts);
650 }
651
DumpHloModuleMetadataIfEnabled(const std::vector<HloModule * > & modules)652 void DumpHloModuleMetadataIfEnabled(const std::vector<HloModule*>& modules) {
653 absl::flat_hash_set<int64> dumped_module_ids;
654 for (const HloModule* module : modules) {
655 CanonicalDebugOptions opts(module->config().debug_options());
656 if (!opts.dump_module_metadata) {
657 continue;
658 }
659 DumpHloModuleMetadata(module->metadata().proto(), opts, &dumped_module_ids);
660 const absl::optional<HloModuleMetadataProto>& prepartitioning_metadata =
661 module->metadata().prepartitioning_metadata();
662 if (prepartitioning_metadata.has_value()) {
663 DumpHloModuleMetadata(*prepartitioning_metadata, opts,
664 &dumped_module_ids);
665 }
666 }
667 }
668
669 } // namespace xla
670