The Op device may be updated if: - A resource touching input is specified: all resource-touching ops run in the device the resource is, regardless of anything else that has been specified. This is identical to the graph mode behavior. - All op inputs are on the CPU, small (<64 elements) and integers (int32/int64). This can be disabled by setting the environment variable "TF_EAGER_ENABLE_SMALL_TEN
| 819 | // (int32/int64). This can be disabled by setting the environment variable |
| 820 | // "TF_EAGER_ENABLE_SMALL_TENSOR_CPU_PINNING" to "0" or "false". |
| 821 | Status MaybeUpdateOpDevice(EagerOperation* op) { |
| 822 | const auto& exempt_ops = InputColocationExemptionRegistry::Global()->Get(); |
| 823 | if (op->is_function() || exempt_ops.find(op->Name()) != exempt_ops.end()) { |
| 824 | // Don't update the device of direct function calls. |
| 825 | // Particularly, if the user did not explicitly request any device for this |
| 826 | // function, picking a device would result in this device being the default |
| 827 | // for nodes inside the function. This is undesirable for multi-device |
| 828 | // functions since the not-explicitly-placed nodes inside the body will all |
| 829 | // end up on this default device. |
| 830 | return Status::OK(); |
| 831 | } |
| 832 | EagerContext* ctx = op->EagerContext(); |
| 833 | bool all_inputs_eligible_for_cpu_pinning = |
| 834 | ctx->PinSmallOpsToCPU() && !op->is_function() && IsPinnableOp(op->Name()); |
| 835 | Device* op_device = op->Device() == nullptr ? ctx->HostCPU() : op->Device(); |
| 836 | for (int i = 0; i < op->Inputs().size(); ++i) { |
| 837 | TensorHandle* tensor_handle = op->Inputs()[i]; |
| 838 | if (tensor_handle->dtype == DT_RESOURCE) { |
| 839 | Device* resource_device = tensor_handle->resource_device(); |
| 840 | VLOG(2) << "for op " << op->Name() << " input " << i << " " |
| 841 | << DataTypeString(tensor_handle->dtype) |
| 842 | << " input device = " << resource_device->name() |
| 843 | << ", op device = " << op_device->name(); |
| 844 | // We check for `op->Device() == nullptr` because it can be later |
| 845 | // interpreted as unspecified device and a different device can |
| 846 | // be selected based on device priority. If any input to an op |
| 847 | // is a resource we must pin it to prevent different device selection. |
| 848 | // TODO(iga): null device can mean "unspecified" or "CPU". Clean this up. |
| 849 | if (resource_device != op_device || op->Device() == nullptr) { |
| 850 | VLOG(1) << (resource_device != op_device ? "Changing " : "Setting ") |
| 851 | << "device of operation " << op->Name() << " to " |
| 852 | << resource_device->name() << " because input #" << i |
| 853 | << " is a resource in this device."; |
| 854 | op->SetDevice(resource_device); |
| 855 | } |
| 856 | all_inputs_eligible_for_cpu_pinning = false; |
| 857 | // No point in looking at other inputs. If there are other resources, |
| 858 | // they must have the same device and we already declared the op to be |
| 859 | // ineligible for CPU pinning. |
| 860 | break; |
| 861 | } else if (all_inputs_eligible_for_cpu_pinning) { |
| 862 | Device* input_device = tensor_handle->DeviceOrHostCPU(ctx); |
| 863 | VLOG(2) << "for op " << op->Name() << " input " << i << " " |
| 864 | << DataTypeString(tensor_handle->dtype) |
| 865 | << " input device = " << input_device->name() |
| 866 | << ", op device = " << op_device->name(); |
| 867 | |
| 868 | // Input is on CPU. |
| 869 | if (input_device != ctx->HostCPU()) { |
| 870 | all_inputs_eligible_for_cpu_pinning = false; |
| 871 | continue; |
| 872 | } |
| 873 | |
| 874 | if (tensor_handle->dtype != DataType::DT_INT32 && |
| 875 | tensor_handle->dtype != DataType::DT_INT64) { |
| 876 | all_inputs_eligible_for_cpu_pinning = false; |
| 877 | continue; |
| 878 | } |
no test coverage detected