Skip to content

Commit

Permalink
[XLA] Add simple HLO if conversion pass
Browse files Browse the repository at this point in the history
kConditional operations are currently generally disallowed in parallel contexts
(e.g. in mapped computations). The julia XLA frontend was running into this limitation
quite a bit, because existing julia code tends to use the terniary operator for select,
e.g. to describe the derivative of a `max` call (and thus a `relu`) - see the
definitions of the derivatives of `max` at
https://github.com/JuliaDiff/DiffRules.jl/blob/master/src/rules.jl#L94

To support these sorts of patterns, add a simple if conversion pass that converts
conditionals in parallel context by equivalent select calls (which are well supported),
i.e. a computation like:

```
if {
 %pif = () parameter(0)
 ROOT %cif = f32[] constant(0)
}

else {
 %pelse = () parameter(0)
 ROOT %celse = f32[] constant(1)
}

mapped {
 %a = f32[] parameter(0)
 %b = f32[] parameter(1)
 %lt = pred[] less-than(%a, %b)
 %t = () tuple()
 ROOT %conditional = f32[] conditional(%lt, %t, %t), true_computation=if, false_computation=else
}

ENTRY comp {
 %p1 = f32[1000]{0} parameter(0)
 %p2 = f32[1000]{0} parameter(1)
 ROOT %mapped = f32[1000]{0} map(%p1, %p2), dimensions={0}, to_apply=mapped
}
```

gets rewritten to

```
mapped {
 %a = f32[] parameter(0)
 %b = f32[] parameter(1)
 %cif = f32[] constant(0)
 %celse = f32[] constant(1)
 %lt = pred[] less-than(%a, %b)
 ROOT %select = f32[] select(%lt, %cif, %celse)
}

ENTRY comp {
 %p1 = f32[1000]{0} parameter(0)
 %p2 = f32[1000]{0} parameter(1)
 ROOT %mapped = f32[1000]{0} map(%p1, %p2) dimensions={0} to_apply=mapped
}
```

To keep things simple, this is accomplished by first rewriting the conditional
to two calls and a select and then inlining the individual calls. Naturally,
the transformation is only applied if the called computation do not
have side effects (which they generally don't if they're in parallel
context). In the future, it would be good to let MapInliner further
simplify this to an implicitly mapped select.
  • Loading branch information
Keno committed Oct 14, 2018
1 parent 771955e commit 735945f
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 0 deletions.
29 changes: 29 additions & 0 deletions tensorflow/compiler/xla/service/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3500,3 +3500,32 @@ tf_cc_test(
"//tensorflow/core:test",
],
)

cc_library(
name = "conditional_to_select",
srcs = ["conditional_to_select.cc"],
hdrs = ["conditional_to_select.h"],
deps = [
":hlo",
":hlo_pass",
":call_inliner",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:types",
"//tensorflow/core:lib",
"@com_google_absl//absl/types:span",
],
)

tf_cc_test(
name = "conditional_to_select_test",
srcs = ["conditional_to_select_test.cc"],
deps = [
":hlo",
":hlo_matchers",
":conditional_to_select",
"//tensorflow/compiler/xla:test",
"//tensorflow/compiler/xla/tests:hlo_verified_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main", # fixdeps: keep
"@com_google_absl//absl/memory",
],
)
88 changes: 88 additions & 0 deletions tensorflow/compiler/xla/service/conditional_to_select.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

#include "tensorflow/compiler/xla/service/conditional_to_select.h"

#include "tensorflow/compiler/xla/service/call_graph.h"
#include "tensorflow/compiler/xla/service/call_inliner.h"
#include "tensorflow/compiler/xla/service/hlo_computation.h"
#include "tensorflow/compiler/xla/service/hlo_instruction.h"
#include "tensorflow/compiler/xla/service/hlo_opcode.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/compiler/xla/types.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/platform/logging.h"

namespace xla {

StatusOr<bool> DoConditionalToSelect(HloInstruction* conditional) {
// Only allow conditional to select if the called computations
// do not have side effects.
for (HloComputation* computation : conditional->called_computations()) {
if (computation->HasSideEffect()) {
VLOG(1) << "Not transforming conditional; branches have side effects:"
<< conditional->ToString();
return false;
}
}

auto computation = conditional->parent();

// Create new instructions
HloInstruction* if_call_op =
computation->AddInstruction(HloInstruction::CreateCall(
conditional->shape(), {conditional->mutable_operand(1)},
conditional->true_computation()));
HloInstruction* else_call_op =
computation->AddInstruction(HloInstruction::CreateCall(
conditional->shape(), {conditional->mutable_operand(2)},
conditional->false_computation()));
HloInstruction* select_op =
computation->AddInstruction(HloInstruction::CreateTernary(
conditional->shape(), HloOpcode::kSelect,
conditional->mutable_operand(0), if_call_op, else_call_op));
conditional->SetupDerivedInstruction(if_call_op);
conditional->SetupDerivedInstruction(else_call_op);
conditional->SetupDerivedInstruction(select_op);
TF_RETURN_IF_ERROR(computation->ReplaceInstruction(conditional, select_op));
TF_RETURN_IF_ERROR(CallInliner::Inline(if_call_op).status());
TF_RETURN_IF_ERROR(CallInliner::Inline(else_call_op).status());
return true;
}

StatusOr<bool> ConditionalToSelect::Run(HloModule* module) {
std::unique_ptr<CallGraph> call_graph = CallGraph::Build(module);
bool did_mutate = false;
VLOG(1) << "Running conditional-to-select pass";
TF_RETURN_IF_ERROR(
call_graph->VisitNodes([&](const CallGraphNode& node) -> Status {
std::vector<HloInstruction*> ToInline;
if (node.context() != CallContext::kParallel) return Status::OK();
for (const CallSite& callsite : node.callsites()) {
if (callsite.instruction()->opcode() == HloOpcode::kConditional) {
VLOG(1) << "Visiting conditional: " << callsite.ToString();
HloInstruction* conditional = callsite.instruction();
TF_ASSIGN_OR_RETURN(bool result,
DoConditionalToSelect(conditional));
did_mutate |= result;
}
}
return Status::OK();
}));
return did_mutate;
}

} // namespace xla
38 changes: 38 additions & 0 deletions tensorflow/compiler/xla/service/conditional_to_select.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

#ifndef TENSORFLOW_COMPILER_XLA_SERVICE_CONDITIONAL_TO_SELECT_H_
#define TENSORFLOW_COMPILER_XLA_SERVICE_CONDITIONAL_TO_SELECT_H_

#include "tensorflow/compiler/xla/service/hlo_module.h"
#include "tensorflow/compiler/xla/service/hlo_pass_interface.h"

namespace xla {

// A pass which transforms conditionals to selects in places where conditionals
// are not allowed to appear (e.g. mapped computation)
class ConditionalToSelect : public HloModulePass {
public:
~ConditionalToSelect() override = default;
absl::string_view name() const override { return "conditional-to-select"; }

// Run conditional to select on the given computation. Returns whether the
// computation was changed.
StatusOr<bool> Run(HloModule* module) override;
};

} // namespace xla

#endif // TENSORFLOW_COMPILER_XLA_SERVICE_CONDITIONAL_TO_SELECT_H_
80 changes: 80 additions & 0 deletions tensorflow/compiler/xla/service/conditional_to_select_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

#include "tensorflow/compiler/xla/service/conditional_to_select.h"

#include <memory>
#include <utility>

#include "absl/memory/memory.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/service/hlo_computation.h"
#include "tensorflow/compiler/xla/service/hlo_instruction.h"
#include "tensorflow/compiler/xla/service/hlo_matchers.h"
#include "tensorflow/compiler/xla/service/hlo_opcode.h"
#include "tensorflow/compiler/xla/test.h"
#include "tensorflow/compiler/xla/tests/hlo_verified_test_base.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"

namespace op = xla::testing::opcode_matchers;

namespace xla {
namespace {

using ConditionalToSelectTest = HloVerifiedTestBase;

// Test that a conditional of simple constants is transformed to a select
TEST_F(ConditionalToSelectTest, MapConditionalConstants) {
const string hlo_text = R"(
HloModule BatchDot
if {
%pif = () parameter(0)
ROOT %cif = f32[] constant(0)
}
else {
%pelse = () parameter(0)
ROOT %celse = f32[] constant(1)
}
mapped {
%a = f32[] parameter(0)
%b = f32[] parameter(1)
%lt = pred[] less-than(%a, %b)
%t = () tuple()
ROOT %conditional = f32[] conditional(%lt, %t, %t), true_computation=if, false_computation=else
}
ENTRY comp {
%p1 = f32[1000]{0} parameter(0)
%p2 = f32[1000]{0} parameter(1)
ROOT %mapped = f32[1000]{0} map(%p1, %p2), dimensions={0}, to_apply=mapped
}
)";

ParseAndVerifyModule(hlo_text);
ConditionalToSelect pass;
ASSERT_TRUE(pass.Run(&module()).ValueOrDie());

HloInstruction* root = module().entry_computation()->root_instruction();
ASSERT_EQ(root->opcode(), HloOpcode::kMap);
HloComputation* mapped = root->called_computations()[0];
EXPECT_THAT(mapped->root_instruction(),
op::Select(op::Lt(op::Parameter(0), op::Parameter(1)),
op::Constant(), op::Constant()));
}
}
}
1 change: 1 addition & 0 deletions tensorflow/compiler/xla/service/cpu/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ cc_library(
"@com_google_absl//absl/types:span",
"//tensorflow/compiler/tf2xla:cpu_function_runtime",
"//tensorflow/compiler/xla/service:map_inliner",
"//tensorflow/compiler/xla/service:conditional_to_select",
"//tensorflow/compiler/xla/service:scatter_expander",
"//tensorflow/compiler/xla:literal",
"//tensorflow/compiler/xla:protobuf_util",
Expand Down
2 changes: 2 additions & 0 deletions tensorflow/compiler/xla/service/cpu/cpu_compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/buffer_liveness.h"
#include "tensorflow/compiler/xla/service/call_inliner.h"
#include "tensorflow/compiler/xla/service/conditional_simplifier.h"
#include "tensorflow/compiler/xla/service/conditional_to_select.h"
#include "tensorflow/compiler/xla/service/convolution_feature_group_converter.h"
#include "tensorflow/compiler/xla/service/cpu/buffer_info_util.h"
#include "tensorflow/compiler/xla/service/cpu/compiler_functor.h"
Expand Down Expand Up @@ -249,6 +250,7 @@ Status CpuCompiler::RunHloPassesThroughLayoutAssn(
&pipeline, module->config().debug_options(),
ReducePrecisionInsertion::PassTiming::BEFORE_OPTIMIZATION);

pipeline.AddPass<ConditionalToSelect>();
pipeline.AddPass<MapInliner>();

// TODO(b/65775800): Fix wrong output bug in Call and remove the CallInliner
Expand Down

0 comments on commit 735945f

Please sign in to comment.