Skip to content

Commit bc399d4

Browse files
authored
Clean up traces for symbolic values caching (#2662)
When the symbolic values caching option is enabled, there are many duplicated calls to prims.eq and prims.shape that appear in any given bsym's subsymbols. DCE is currently applied before the decent to the subsymbols happens. When the descent to the subsymbols happens, it results in a very ugly and hard to read trace. This PR applies dce to the bsym's subsymbols earlier on to tidy things up. Fixes #2728
1 parent 8236d1c commit bc399d4

6 files changed

Lines changed: 92 additions & 29 deletions

File tree

‎thunder/core/rematerialization.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ def rematerialize(trace: TraceCtx) -> TraceCtx:
458458
computed_cuts_for_producers[producer] += cut
459459

460460
rematerialized_trace = from_trace(trace)
461-
rematerialized_trace.bound_symbols = tuple(new_bsyms.get(bsym, bsym) for bsym in trace.bound_symbols)
461+
rematerialized_trace.bound_symbols = list(new_bsyms.get(bsym, bsym) for bsym in trace.bound_symbols)
462462

463463
end_time_ns = time.perf_counter_ns()
464464
elapsed_time_ns = end_time_ns - start_time_ns

‎thunder/core/symbol.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,12 @@ def tag_tensorproxy_output_as_detached(proxy):
350350
exception_type=AssertionError,
351351
)
352352

353+
# When using symbolic values, there may be duplicate prims.eq and prims.shape subsymbols that can be removed.
354+
from thunder.core.transform_common import dce_bsyms
355+
356+
subsymbols = dce_bsyms(subsymbols, result)
357+
bsym = bsym.from_bsym(subsymbols=subsymbols)
358+
353359
symbols_list.append(bsym)
354360
return result
355361

‎thunder/core/transform_common.py‎

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,20 +142,32 @@ def keep_or_swap(p):
142142
# that only produce non-proxy objects
143143
# NOTE needed_proxies is an in/out argument, it takes an initial set of Variables you want to keep, and return
144144
# all the needed proxies of the input trace
145-
def dce(trace: Trace, needed_proxies: None | set[Variable] = None) -> Trace:
146-
start_time_ns = time.perf_counter_ns()
145+
def dce_bsyms(
146+
bsyms: list[BoundSymbolInterface],
147+
output: Any,
148+
needed_proxies: None | set[Variable] = None,
149+
) -> Trace | list[BoundSymbolInterface]:
150+
"""Runs a Dead Code Elimination (DCE) pass
151+
152+
Args:
153+
bsyms: The list of bound symbols to run the DCE pass on.
154+
needed_proxies: The set of variables to keep.
155+
output: The output of the list of bound symbols.
147156
148-
producer_map: ProxyDict = producers(trace)
157+
Returns:
158+
The list of bound symbols after the DCE pass.
159+
"""
160+
producer_map: ProxyDict = producers(bsyms)
149161

150-
flat_trace_outputs, _ = tree_flatten(trace.output)
162+
flat_trace_outputs, _ = tree_flatten(output)
151163
if needed_proxies is None:
152164
needed_proxies: set[Variable] = set(tuple(variableify(x) for x in flat_trace_outputs if isinstance(x, Proxy)))
153165
else:
154166
needed_proxies.update(tuple(variableify(x) for x in flat_trace_outputs if isinstance(x, Proxy)))
155167
dced = []
156168

157169
bsym: BoundSymbol
158-
for bsym in reversed(trace.bound_symbols):
170+
for bsym in reversed(bsyms):
159171
# Preserves symbols that should never be collected
160172
if has_tags(bsym, {prims.OpTags.DONT_DCE}):
161173
needed = True
@@ -182,19 +194,28 @@ def dce(trace: Trace, needed_proxies: None | set[Variable] = None) -> Trace:
182194
for x in nbsym.flat_proxy_args:
183195
needed_proxies.add(variableify(x))
184196

185-
dcetrace = from_trace(trace)
186197
dced_bound_symbols = list(reversed(dced))
187198
# duplicate number proxies happen with the symbolic shapes and are
188199
# not covered by the above (due to being in tuples?).
189200
dced_bound_symbols = remove_duplicate_number_proxies(dced_bound_symbols)
190-
dcetrace.bound_symbols = dced_bound_symbols
201+
202+
return dced_bound_symbols
203+
204+
205+
def dce(trace: Trace, needed_proxies: set[Variable] = None) -> Trace:
206+
start_time_ns = time.perf_counter_ns()
207+
208+
bsyms = trace.bound_symbols
209+
dced_bsyms = dce_bsyms(bsyms, trace.output, needed_proxies)
210+
result = from_trace(trace)
211+
result.bound_symbols = dced_bsyms
191212

192213
end_time_ns = time.perf_counter_ns()
193214
elapsed_time_ns = end_time_ns - start_time_ns
194215
elapsed_time_millis = elapsed_time_ns // 1000000
195-
dcetrace.set_provenance(TraceProvenance(f"Dead Code Elimination (took {elapsed_time_millis} milliseconds)"))
196216

197-
return dcetrace
217+
result.set_provenance(TraceProvenance(f"Dead Code Elimination (took {elapsed_time_millis} milliseconds)"))
218+
return result
198219

199220

200221
#

‎thunder/dynamo/utils.py‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -259,20 +259,20 @@ def get_backed_value(s):
259259
return tuple(map(get_backed_value, vals))
260260

261261

262-
def get_proxy_inputs_from_node(node: torch.fx.Node) -> tuple[tuple, dict]:
262+
def get_proxy_inputs_from_node(node: torch.fx.Node, tracectx) -> tuple[tuple, dict]:
263263
"""Creates proxy inputs from a torch.fx.Node for use with Thunder.
264264
265265
This function generates proxy inputs for a given torch.fx.Node
266266
267267
Args:
268268
node (torch.fx.Node): The FX graph node to create proxy inputs for.
269+
tracectx (TraceCtx): The trace context to use to generate proxy inputs.
269270
"""
270271
import thunder
271-
from thunder.core.trace import TraceCtx
272272
from thunder.core.proxies import proxy
273273

274274
# We need to be under trace context to generate proxies.
275-
with thunder.core.trace.tracectx(TraceCtx()):
275+
with thunder.core.trace.tracectx(tracectx):
276276

277277
def make_input_proxy(arg_node):
278278
# This is a Node in the graph representing a Tensor or tuple of Tensors or
@@ -380,8 +380,10 @@ def _run_with_cache_info():
380380
cache_info["default_dtype"] = torch.get_default_dtype()
381381
cache_info["default_device"] = torch.get_default_device()
382382

383+
tracectx = TraceCtx()
384+
383385
try:
384-
proxy_args, proxy_kwargs = get_proxy_inputs_from_node(node)
386+
proxy_args, proxy_kwargs = get_proxy_inputs_from_node(node, tracectx)
385387
except Exception as e:
386388
return False, SplitReason(
387389
SplitReasonType.EXCEPTION_PROXY_THUNDER_OP,
@@ -395,7 +397,7 @@ def _run_with_cache_info():
395397
else thunder_symbol
396398
)
397399
# We need to be under trace context to generate proxies.
398-
with thunder.core.trace.tracectx(TraceCtx()):
400+
with thunder.core.trace.tracectx(tracectx):
399401
try:
400402
function_to_run(*proxy_args, **proxy_kwargs)
401403
except Exception as e:
@@ -478,6 +480,7 @@ def is_node_supported_by_thunder(
478480
"""
479481
Determine whether thunder can execute the operation described by this node.
480482
"""
483+
from thunder.core.trace import TraceCtx
481484
# Docs from the torch.fx.Node - https://pytorch.org/docs/stable/fx.html#torch.fx.Node
482485
# Each Node has a function specified by its op property
483486
# Below are the details for the ones this function is interested in -
@@ -555,7 +558,7 @@ def is_node_supported_by_thunder(
555558
if torchctx.has_method(node.target):
556559
# `torchctx.get_method` requires args and kwargs to resolve which overload of the method is picked.
557560
try:
558-
args, kwargs = get_proxy_inputs_from_node(node)
561+
args, kwargs = get_proxy_inputs_from_node(node, TraceCtx())
559562
except Exception as e:
560563
return False, SplitReason(
561564
SplitReasonType.EXCEPTION_PROXY_THUNDER_OP,

‎thunder/executors/nvfuserex_impl.py‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from thunder.core.trace import TraceCtx, from_trace, TraceProvenance
4444
from thunder.core.symbol import BoundSymbol, BoundSymbolRHS, Symbol, has_tags
4545
from thunder.core.devices import Device, DeviceType, cpu
46-
from thunder.core.transform_common import dce, cse_single_bsym, replace_redundant_inputs
46+
from thunder.core.transform_common import dce, dce_bsyms, cse_single_bsym, replace_redundant_inputs
4747
from thunder.core.profile import annotate_for_profile
4848
from thunder.core.compile_data import get_compile_option
4949
from thunder.torch.experimental.dtensor_torch_and_prims import DTensorPrimIDs
@@ -707,14 +707,11 @@ def has_cuda_input_or_output(self, bsym: BoundSymbol) -> bool:
707707
return False
708708

709709
def _dce_bsyms(self, input_list, output, bsyms: list[BoundSymbol]) -> list[BoundSymbol]:
710-
trace = TraceCtx(None)
711-
trace.bound_symbols = bsyms
712-
bsyms.append(prims.python_return.bind(output, output=None))
713710
needed_proxies: set[Variable] = set()
714-
trace = dce(trace, needed_proxies)
711+
bsyms = dce_bsyms(bsyms, output, needed_proxies)
715712
# update the input_list by removing the unused inputs
716713
input_list[:] = [x for x in input_list if variableify(x) in needed_proxies]
717-
return list(filter(lambda x: x.sym != prims.python_return, trace.bound_symbols))
714+
return bsyms
718715

719716
def fuse(self, region: Region, fusion_counter: int) -> BoundSymbol:
720717
sorted_unique_inputs: list[Proxy] = [unvariableify(x) for x in region.inputs]

‎thunder/tests/test_core.py‎

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,6 +2170,36 @@ def func(x, y, device):
21702170
assert [t.name for t in tree_flatten(flatten_cse_trace.output)[0]] == ["t4", "t4", "t6", "t14", "t15", "t16", "t17"]
21712171

21722172

2173+
@instantiate(
2174+
dtypes=NOTHING,
2175+
)
2176+
def test_dce(executor, device, _):
2177+
def func(x):
2178+
dead_code = x + 1 # noqa: F841
2179+
y = x * x
2180+
return y
2181+
2182+
x = make_tensor((2, 2), device=device, dtype=torch.float32)
2183+
compiled = thunder.jit(func, executors=executor.executors_list())
2184+
compiled(x)
2185+
traces = thunder.last_traces(compiled)
2186+
2187+
# find last trace before DCE is applied
2188+
for i, trace in enumerate(traces):
2189+
provenance = trace.get_provenance().pss if trace.get_provenance() else ""
2190+
if "Dead Code Elimination" in provenance:
2191+
break
2192+
i -= 1
2193+
trace = traces[i]
2194+
2195+
from thunder.core.transform_common import dce, dce_bsyms
2196+
2197+
dced_trace = dce(trace)
2198+
dced_bsyms = dce_bsyms(trace.bound_symbols, trace.output)
2199+
assert len(dced_trace.bound_symbols) == len(trace.bound_symbols) - 1
2200+
assert len(dced_trace.bound_symbols) == len(dced_bsyms)
2201+
2202+
21732203
def test_symbol_flat_args():
21742204
from thunder.core.symbol import Symbol, BoundSymbol
21752205

@@ -3295,22 +3325,28 @@ def clean(tr):
32953325

32963326

32973327
def test_prims_pack_list():
3298-
def foo():
3299-
pass
3300-
3301-
trace = TraceCtx(foo)
3328+
def foo(x):
3329+
a, b = x
3330+
return [a, b]
33023331

33033332
a = torch.randn(2, 2)
33043333
b = torch.randn(2, 2)
33053334

3335+
jfoo = thunder.jit(foo)
3336+
jfoo((a, b))
3337+
3338+
trace = thunder.last_traces(jfoo)[-1]
3339+
3340+
return_bsym = trace.bound_symbols[-1]
3341+
trace.bound_symbols = trace.bound_symbols[:-1]
3342+
33063343
with tracectx(trace):
3307-
x = prims.unpack_trivial(a, name="x")
3308-
y = prims.unpack_trivial(b, name="y")
3344+
x, y = return_bsym.flat_args
33093345
packed_list = prims.pack_list(x, y)
33103346
prims.python_return(packed_list)
33113347

33123348
func = trace.python_callable()
3313-
actual = func()
3349+
actual = func(a, b)
33143350
expected = [a, b]
33153351

33163352
assert isinstance(actual, list) and actual == expected

0 commit comments

Comments
 (0)