| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 2c26c98 commit 43c428a
15 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -548,7 +548,8 @@ Status DirectSession::Run(const RunOptions& run_options, | |||
| 548 | 548 | ((measure_step_count + 1) % build_cost_model_every == 0); | |
| 549 | 549 | } | |
| 550 | 550 | } | |
| 551 | - if (do_trace || update_cost_model) { | ||
| 551 | + if (do_trace || update_cost_model || | ||
| 552 | + run_options.report_tensor_allocations_upon_oom()) { | ||
| 552 | 553 | run_state.collector.reset( | |
| 553 | 554 | new StepStatsCollector(run_metadata->mutable_step_stats())); | |
| 554 | 555 | args.stats_collector = run_state.collector.get(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1804,6 +1804,21 @@ Status ExecutorState::ProcessOutputs(const NodeItem& item, OpKernelContext* ctx, | |||
| 1804 | 1804 | LOG(WARNING) << this << " Compute status: " << s; | |
| 1805 | 1805 | DumpState(); | |
| 1806 | 1806 | } | |
| 1807 | + if (s.code() == error::RESOURCE_EXHAUSTED) { | ||
| 1808 | + if (stats_collector_) { | ||
| 1809 | + string err = stats_collector_->ReportAllocsOnResourceExhausted( | ||
| 1810 | + s.error_message()); | ||
| 1811 | + s = Status(s.code(), strings::StrCat(s.error_message(), err)); | ||
| 1812 | + } else { | ||
| 1813 | + s = Status( | ||
| 1814 | + s.code(), | ||
| 1815 | + strings::StrCat( | ||
| 1816 | + s.error_message(), | ||
| 1817 | + "\nHint: If you want to see a list of allocated tensors when " | ||
| 1818 | + "OOM happens, add report_tensor_allocations_upon_oom " | ||
| 1819 | + "to RunOptions for current allocation info.\n")); | ||
| 1820 | + } | ||
| 1821 | + } | ||
| 1807 | 1822 | return s; | |
| 1808 | 1823 | } | |
| 1809 | 1824 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,10 +20,21 @@ limitations under the License. | |||
| 20 | 20 | #include "tensorflow/core/framework/tracking_allocator.h" | |
| 21 | 21 | #include "tensorflow/core/graph/costmodel.h" | |
| 22 | 22 | #include "tensorflow/core/lib/core/stringpiece.h" | |
| 23 | + #include "tensorflow/core/lib/strings/numbers.h" | ||
| 23 | 24 | #include "tensorflow/core/lib/strings/scanner.h" | |
| 24 | 25 | #include "tensorflow/core/platform/logging.h" | |
| 25 | 26 | ||
| 26 | 27 | namespace tensorflow { | |
| 28 | + namespace { | ||
| 29 | + const int kMaxAllocReportNodes = 100; | ||
| 30 | + const float kMaxAllocReportFraction = 0.99; | ||
| 31 | + | ||
| 32 | + struct AllocStats { | ||
| 33 | + std::map<int64, std::vector<string>> nodes_by_size; | ||
| 34 | + int64 total_bytes = 0; | ||
| 35 | + int64 total_nodes = 0; | ||
| 36 | + }; | ||
| 37 | + } // namespace | ||
| 27 | 38 | ||
| 28 | 39 | NodeExecStatsWrapper::NodeExecStatsWrapper() | |
| 29 | 40 | : NodeExecStatsWrapper(new NodeExecStats) {} | |
@@ -267,6 +278,85 @@ void StepStatsCollector::Save(const string& device, | |||
| 267 | 278 | } | |
| 268 | 279 | } | |
| 269 | 280 | ||
| 281 | + string StepStatsCollector::ReportAllocsOnResourceExhausted(const string& err) { | ||
| 282 | + mutex_lock l(mu_); | ||
| 283 | + if (err.find("OOM") == err.npos) { | ||
| 284 | + return ""; | ||
| 285 | + } | ||
| 286 | + // <device, allocator> -> AllocStats | ||
| 287 | + std::map<std::pair<string, string>, AllocStats> allocs_map; | ||
| 288 | + string report = "\n"; | ||
| 289 | + for (const auto& dev_stat : dev_stats_) { | ||
| 290 | + const string& device = dev_stat.first; | ||
| 291 | + // Only print the device that has OOM. | ||
| 292 | + // TODO(xpan): Extract device from err first to speed it up. | ||
| 293 | + if (err.find(device) == err.npos) { | ||
| 294 | + continue; | ||
| 295 | + } | ||
| 296 | + // NodeExecStatsWrapper* | ||
| 297 | + for (const auto& stats : dev_stat.second) { | ||
| 298 | + // std::pair<AllocatorMemoryUsed*, TrackingAllocator*> | ||
| 299 | + for (const auto& alloc : stats->allocations_) { | ||
| 300 | + // Only print the allocator that has OOM. | ||
| 301 | + // TODO(xpan): Extract device from err first to speed it up. | ||
| 302 | + if (err.find(alloc.first->allocator_name()) == err.npos) { | ||
| 303 | + continue; | ||
| 304 | + } | ||
| 305 | + auto dev_allocator = | ||
| 306 | + std::make_pair(dev_stat.first, alloc.first->allocator_name()); | ||
| 307 | + AllocStats& dev_allocs_stats = allocs_map[dev_allocator]; | ||
| 308 | + TrackingAllocator* tracking_alloc = alloc.second; | ||
| 309 | + gtl::InlinedVector<AllocRecord, 4> cur_records = | ||
| 310 | + tracking_alloc->GetCurrentRecords(); | ||
| 311 | + int64 cur_bytes = 0; | ||
| 312 | + for (const auto& r : cur_records) { | ||
| 313 | + cur_bytes += r.alloc_bytes; | ||
| 314 | + } | ||
| 315 | + if (cur_bytes > 0) { | ||
| 316 | + dev_allocs_stats.total_bytes += cur_bytes; | ||
| 317 | + dev_allocs_stats.total_nodes++; | ||
| 318 | + dev_allocs_stats.nodes_by_size[cur_bytes].push_back( | ||
| 319 | + stats->stats()->node_name()); | ||
| 320 | + } | ||
| 321 | + } | ||
| 322 | + } | ||
| 323 | + } | ||
| 324 | + | ||
| 325 | + for (const auto& dev_allocs_it : allocs_map) { | ||
| 326 | + const auto& dev = dev_allocs_it.first; | ||
| 327 | + const AllocStats& dev_allocs_stats = dev_allocs_it.second; | ||
| 328 | + int64 reported_bytes = 0; | ||
| 329 | + int64 reported_nodes = 0; | ||
| 330 | + bool done = false; | ||
| 331 | + strings::StrAppend(&report, "\nCurrent usage from device: ", dev.first, | ||
| 332 | + ", allocator: ", dev.second, "\n"); | ||
| 333 | + // Print allocations stats of the <device, allocator> pair. | ||
| 334 | + for (auto it = dev_allocs_stats.nodes_by_size.rbegin(); | ||
| 335 | + it != dev_allocs_stats.nodes_by_size.rend(); ++it) { | ||
| 336 | + for (const string& node_name : it->second) { | ||
| 337 | + reported_bytes += it->first; | ||
| 338 | + strings::StrAppend(&report, " ", | ||
| 339 | + strings::HumanReadableNumBytes(it->first), " from ", | ||
| 340 | + node_name, "\n"); | ||
| 341 | + if (++reported_nodes > kMaxAllocReportNodes || | ||
| 342 | + reported_bytes >= | ||
| 343 | + dev_allocs_stats.total_bytes * kMaxAllocReportFraction) { | ||
| 344 | + done = true; | ||
| 345 | + break; | ||
| 346 | + } | ||
| 347 | + } | ||
| 348 | + if (done) break; | ||
| 349 | + } | ||
| 350 | + int64 remain_nodes = dev_allocs_stats.total_nodes - reported_nodes; | ||
| 351 | + int64 remain_bytes = dev_allocs_stats.total_bytes - reported_bytes; | ||
| 352 | + if (remain_nodes > 0) { | ||
| 353 | + strings::StrAppend(&report, " Remaining ", remain_nodes, " nodes with ", | ||
| 354 | + strings::HumanReadableNumBytes(remain_bytes), "\n"); | ||
| 355 | + } | ||
| 356 | + } | ||
| 357 | + return report; | ||
| 358 | + } | ||
| 359 | + | ||
| 270 | 360 | void StepStatsCollector::Finalize() { | |
| 271 | 361 | mutex_lock l(mu_); | |
| 272 | 362 | FinalizeInternal(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -82,6 +82,13 @@ class StepStatsCollector { | |||
| 82 | 82 | void Save(const string& device, NodeExecStats* nt); | |
| 83 | 83 | void Save(const string& device, NodeExecStatsWrapper* stats); | |
| 84 | 84 | ||
| 85 | + // Generates a string reporting the currently used memory based | ||
| 86 | + // on ResourceExhausted OOM `err` message. | ||
| 87 | + // `err` message needs to contain device name and allocator name, E.g.: | ||
| 88 | + // "ResourceExhaustedError: OOM when allocating tensor ... | ||
| 89 | + // on /job:localhost/replica:0/task:0/device:GPU:0 by allocator GPU_0_bfc" | ||
| 90 | + string ReportAllocsOnResourceExhausted(const string& err); | ||
| 91 | + | ||
| 85 | 92 | // The following 2 Finalize methods populate the StepStats passed | |
| 86 | 93 | // from the constructor. Calling it more than once won't have any effect. | |
| 87 | 94 | // User shouldn't call Save() methods after Finalize. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -498,6 +498,9 @@ Status MasterSession::ReffedClientGraph::RunPartitions( | |||
| 498 | 498 | ||
| 499 | 499 | // Collect execution cost stats on a smoothly decreasing frequency. | |
| 500 | 500 | ExecutorOpts exec_opts; | |
| 501 | + if (pss->report_tensor_allocations_upon_oom) { | ||
| 502 | + exec_opts.set_report_tensor_allocations_upon_oom(true); | ||
| 503 | + } | ||
| 501 | 504 | if (pss->collect_costs) { | |
| 502 | 505 | exec_opts.set_record_costs(true); | |
| 503 | 506 | } | |
@@ -1368,6 +1371,8 @@ Status MasterSession::DoPartialRun(CallOptions* opts, | |||
| 1368 | 1371 | const auto count = run_state->count; | |
| 1369 | 1372 | pss.collect_timeline = | |
| 1370 | 1373 | req.options().trace_level() == RunOptions::FULL_TRACE; | |
| 1374 | + pss.report_tensor_allocations_upon_oom = | ||
| 1375 | + req.options().report_tensor_allocations_upon_oom(); | ||
| 1371 | 1376 | ||
| 1372 | 1377 | // Build the cost model every 'build_cost_model_every' steps after skipping | |
| 1373 | 1378 | // an | |
@@ -1528,7 +1533,8 @@ Status MasterSession::DoRunWithLocalExecution( | |||
| 1528 | 1533 | TRACEPRINTF("stepid %llu", step_id); | |
| 1529 | 1534 | ||
| 1530 | 1535 | pss.collect_timeline = req.options().trace_level() == RunOptions::FULL_TRACE; | |
| 1531 | - | ||
| 1536 | + pss.report_tensor_allocations_upon_oom = | ||
| 1537 | + req.options().report_tensor_allocations_upon_oom(); | ||
| 1532 | 1538 | // Build the cost model every 'build_cost_model_every' steps after skipping an | |
| 1533 | 1539 | // initial 'build_cost_model_after' steps. | |
| 1534 | 1540 | const int64 build_cost_model_after = | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -146,6 +146,7 @@ class MasterSession : public core::RefCounted { | |||
| 146 | 146 | bool collect_timeline = false; | |
| 147 | 147 | bool collect_rpcs = false; | |
| 148 | 148 | bool collect_partition_graphs = false; | |
| 149 | + bool report_tensor_allocations_upon_oom = false; | ||
| 149 | 150 | Microseconds start_micros = Microseconds(0); | |
| 150 | 151 | Microseconds end_micros = Microseconds(0); | |
| 151 | 152 | std::vector<StepStats> step_stats; // per partition | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -132,7 +132,8 @@ void Worker::DoRunGraph(CallOptions* opts, RunGraphRequestWrapper* request, | |||
| 132 | 132 | return; | |
| 133 | 133 | } | |
| 134 | 134 | StepStatsCollector* collector = nullptr; | |
| 135 | - if (request->exec_opts().record_timeline() || | ||
| 135 | + if (request->exec_opts().report_tensor_allocations_upon_oom() || | ||
| 136 | + request->exec_opts().record_timeline() || | ||
| 136 | 137 | request->exec_opts().record_costs()) { | |
| 137 | 138 | collector = new StepStatsCollector(response->mutable_step_stats()); | |
| 138 | 139 | // TODO(mrry,pbar): GPU tracing for distributed steps. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -622,8 +622,10 @@ Status OpKernelContext::allocate_tensor( | |||
| 622 | 622 | Tensor new_tensor(a, type, shape, logged_attr); | |
| 623 | 623 | ||
| 624 | 624 | if (!new_tensor.IsInitialized()) { | |
| 625 | - return errors::ResourceExhausted("OOM when allocating tensor with shape", | ||
| 626 | - shape.DebugString()); | ||
| 625 | + return errors::ResourceExhausted( | ||
| 626 | + "OOM when allocating tensor with shape", shape.DebugString(), | ||
| 627 | + " and type ", DataTypeString(type), " on ", params_->device->name(), | ||
| 628 | + " by allocator ", a->Name()); | ||
| 627 | 629 | } | |
| 628 | 630 | if (params_->log_memory) { | |
| 629 | 631 | LogMemory::RecordTensorAllocation(params_->op_kernel->name(), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -183,6 +183,17 @@ gtl::InlinedVector<AllocRecord, 4> TrackingAllocator::GetRecordsAndUnRef() { | |||
| 183 | 183 | return allocations; | |
| 184 | 184 | } | |
| 185 | 185 | ||
| 186 | + gtl::InlinedVector<AllocRecord, 4> TrackingAllocator::GetCurrentRecords() { | ||
| 187 | + gtl::InlinedVector<AllocRecord, 4> allocations; | ||
| 188 | + { | ||
| 189 | + mutex_lock lock(mu_); | ||
| 190 | + for (const AllocRecord& alloc : allocations_) { | ||
| 191 | + allocations.push_back(alloc); | ||
| 192 | + } | ||
| 193 | + } | ||
| 194 | + return allocations; | ||
| 195 | + } | ||
| 196 | + | ||
| 186 | 197 | bool TrackingAllocator::UnRef() { | |
| 187 | 198 | CHECK_GE(ref_, 1); | |
| 188 | 199 | --ref_; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -85,6 +85,8 @@ class TrackingAllocator : public Allocator { | |||
| 85 | 85 | // deallocated. After this call completes and all allocated pointers | |
| 86 | 86 | // have been deallocated the wrapper will delete itself. | |
| 87 | 87 | gtl::InlinedVector<AllocRecord, 4> GetRecordsAndUnRef(); | |
| 88 | + // Returns a copy of allocation records collected so far. | ||
| 89 | + gtl::InlinedVector<AllocRecord, 4> GetCurrentRecords(); | ||
| 88 | 90 | ||
| 89 | 91 | protected: | |
| 90 | 92 | ~TrackingAllocator() override {} | |
| Back | FazBrowse Home | New Git URL |
0 commit comments