Skip to content

Commit c62159d

Browse files
authored
[Feat] Add fx.known_block_size() as constant accessor (#968)
* [Feat] Add fx.known_block_size() as constant accessor * accept None as the trace_option value
1 parent 47ed57a commit c62159d

7 files changed

Lines changed: 174 additions & 26 deletions

File tree

python/flydsl/compiler/jit_function.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from .._mlir.passmanager import PassManager
2323
from ..expr.meta import tracing_context
2424
from ..expr.typing import Constexpr, Stream
25-
from ..expr.utils.arith import fastmath as fastmath_ctx
2625
from ..utils import env, log
2726
from ..utils.file import atomic_write
2827
from .ast_rewriter import ASTRewriter
@@ -1511,12 +1510,11 @@ def __call__(self, *args, **kwargs):
15111510
log().info(f"dsl_args={dsl_args}")
15121511
named_args = dict(zip(param_names, dsl_args))
15131512
named_args.update(constexpr_values)
1514-
fastmath_flag = effective_fastmath_hint(CompilationContext.get_compile_hints())
1515-
fastmath_scope = (
1516-
fastmath_ctx(fastmath_flag) if fastmath_flag is not None else nullcontext()
1517-
)
15181513
# Bound the call-site boundary at the jit body.
1519-
with tracing_context(self.func), fastmath_scope:
1514+
with tracing_context(
1515+
self.func,
1516+
fastmath=effective_fastmath_hint(CompilationContext.get_compile_hints()),
1517+
):
15201518
if bound_self is not None:
15211519
self.func(bound_self, **named_args)
15221520
else:

python/flydsl/compiler/kernel_function.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import inspect
55
import threading
6-
from contextlib import contextmanager, nullcontext
6+
from contextlib import contextmanager
77
from functools import partial
88
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
99

@@ -12,7 +12,6 @@
1212
from ..expr.meta import capture_user_location, file_location, tracing_context
1313
from ..expr.numeric import Index, Integer
1414
from ..expr.typing import Constexpr, as_ir_value
15-
from ..expr.utils.arith import fastmath as fastmath_ctx
1615
from .ast_rewriter import ASTRewriter
1716
from .diagnostics import install_excepthook, warn_annotation_value_mismatch, warn_invalid_annotations
1817
from .jit_argument import is_type_param_annotation, resolve_signature
@@ -590,10 +589,13 @@ def _emit_kernel(
590589

591590
dsl_args.update(constexpr_values)
592591

593-
fastmath_flag = effective_fastmath_hint(CompilationContext.get_compile_hints())
594-
fastmath_scope = fastmath_ctx(fastmath_flag) if fastmath_flag is not None else nullcontext()
595-
# Bound the call-site boundary at the kernel body.
596-
with tracing_context(self._func), fastmath_scope:
592+
# Bound the call-site boundary at the kernel body and carry
593+
# the ambient tracing options into it.
594+
with tracing_context(
595+
self._func,
596+
fastmath=effective_fastmath_hint(CompilationContext.get_compile_hints()),
597+
known_block_size=tuple(known_block_size) if known_block_size is not None else None,
598+
):
597599
if bound_self is not None:
598600
self._func(bound_self, **dsl_args)
599601
else:

python/flydsl/expr/gpu.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .._mlir.dialects._fly_enum_gen import AddressSpace
2222
from ..compiler.protocol import dsl_align_of, dsl_size_of
2323
from .math import dsl_math_wrap_result
24-
from .meta import dsl_loc_tracing
24+
from .meta import dsl_loc_tracing, tracing_option
2525
from .numeric import Int32, Numeric, Uint8
2626
from .primitive import get_dyn_shared, make_ptr
2727
from .struct import (
@@ -45,6 +45,7 @@
4545
"shuffle_up",
4646
"shuffle_down",
4747
"shuffle_idx",
48+
"known_block_size",
4849
"SharedAllocator",
4950
]
5051

@@ -103,6 +104,17 @@ def shuffle_idx(value, lane, width):
103104
return shuffle(value, lane, width, mode="idx")
104105

105106

107+
def known_block_size():
108+
"""Return the compile-time block dimensions as ``(x, y, z)`` Python ints.
109+
110+
Raises ``RuntimeError`` when no block size is in scope.
111+
"""
112+
size = tracing_option("known_block_size")
113+
if size is None:
114+
raise RuntimeError("no compile-time block size is in scope.")
115+
return size
116+
117+
106118
thread_idx = Tuple3D(gpu.thread_id)
107119
block_idx = Tuple3D(gpu.block_id)
108120
block_dim = Tuple3D(gpu.block_dim)

python/flydsl/expr/meta.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"dsl_loc_tracing",
2323
"dsl_wrap_result",
2424
"tracing_context",
25+
"tracing_option",
2526
]
2627

2728
# Package root for the ``flydsl`` Python package: ``.../python/flydsl``.
@@ -54,8 +55,20 @@ def _stack():
5455

5556

5657
@contextlib.contextmanager
57-
def tracing_context(func):
58-
_stack().append(getattr(func, "__code__", None))
58+
def tracing_context(func=None, **options):
59+
"""Push a tracing frame for the duration of the ``with`` block.
60+
61+
*func*, when given, marks the call-site boundary used by
62+
``capture_user_location``; a frame entered without one inherits the
63+
enclosing boundary. Keyword *options* become ambient settings readable
64+
with ``tracing_option(key)``. Every key passed is set, ``None`` included
65+
-- an explicit ``None`` shadows an enclosing setting.
66+
"""
67+
stack = _stack()
68+
boundary = getattr(func, "__code__", None)
69+
if boundary is None and stack:
70+
boundary = stack[-1][0]
71+
stack.append((boundary, options))
5972
try:
6073
yield
6174
finally:
@@ -64,6 +77,16 @@ def tracing_context(func):
6477
stack.pop()
6578

6679

80+
def tracing_option(key, default=None):
81+
"""Return the ambient value of *key*, innermost tracing frame first."""
82+
stack = getattr(_tls, "stack", None)
83+
if stack:
84+
for _, options in reversed(stack):
85+
if key in options:
86+
return options[key]
87+
return default
88+
89+
6790
def file_location(filename: str, line: int, col: int = 0, context=None) -> ir.Location:
6891
ctx = context or ir.Context.current
6992
if filename and not filename.startswith("<"):
@@ -79,7 +102,7 @@ def capture_user_location() -> ir.Location:
79102
tracing boundary.
80103
"""
81104
stack = getattr(_tls, "stack", None)
82-
boundary = stack[-1] if stack else None
105+
boundary = stack[-1][0] if stack else None
83106
max_depth = env.debug.max_loc_depth
84107
ctx = ir.Context.current
85108
locs = []

python/flydsl/expr/utils/arith.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@
33

44
import builtins
55
import contextlib
6-
import threading
76
from functools import partialmethod
87

98
from ..._mlir import ir
109
from ..._mlir.dialects import arith, math
1110
from ..._mlir.extras import types as T
12-
from ..meta import dsl_loc_tracing
11+
from ..meta import dsl_loc_tracing, tracing_context, tracing_option
1312

1413
# --------------------------------------------------------------------------- #
15-
# Ambient fastmath context (thread-local)
14+
# Ambient fastmath context (a ``tracing_context`` option)
1615
# --------------------------------------------------------------------------- #
17-
_fm_tls = threading.local()
1816

1917

2018
def _normalize_fastmath(flags):
@@ -37,7 +35,7 @@ def _normalize_fastmath(flags):
3735

3836
def current_fastmath():
3937
"""Return the ambient fastmath flags set by ``fastmath(...)``, or ``None``."""
40-
return getattr(_fm_tls, "value", None)
38+
return _normalize_fastmath(tracing_option("fastmath"))
4139

4240

4341
def resolve_fastmath(explicit):
@@ -48,12 +46,8 @@ def resolve_fastmath(explicit):
4846
@contextlib.contextmanager
4947
def fastmath(flags):
5048
"""Apply *flags* to floating-point ops built inside the ``with`` block."""
51-
prev = getattr(_fm_tls, "value", None)
52-
_fm_tls.value = _normalize_fastmath(flags)
53-
try:
49+
with tracing_context(fastmath=_normalize_fastmath(flags)):
5450
yield
55-
finally:
56-
_fm_tls.value = prev
5751

5852

5953
def element_type(ty) -> ir.Type:

tests/unit/test_kernel_known_block_size.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,74 @@ def _launch_big(x: fx.Tensor, stream: fx.Stream = fx.Stream(None)):
285285

286286
source_ir = _get_source_ir(_launch_big, self.x)
287287
assert "known_block_size = array<i32: 512, 1, 1>" in source_ir
288+
289+
290+
class TestKnownBlockSizeTraceAccessor:
291+
"""Verify the trace-time ``fx.known_block_size()`` view of the same value."""
292+
293+
@pytest.fixture(autouse=True)
294+
def _setup(self):
295+
self.x = torch.zeros(64, device="cuda", dtype=torch.float32)
296+
297+
def test_raises_outside_a_kernel(self):
298+
with pytest.raises(RuntimeError, match="no compile-time block size"):
299+
fx.known_block_size()
300+
301+
def test_sees_the_declared_size(self):
302+
seen = []
303+
304+
@flyc.kernel(known_block_size=[128, 4, 2])
305+
def _kn(x: fx.Tensor):
306+
seen.append(fx.known_block_size())
307+
308+
@flyc.jit
309+
def _launch(x: fx.Tensor, stream: fx.Stream = fx.Stream(None)):
310+
_kn(x).launch(grid=(1, 1, 1), block=(128, 4, 2), stream=stream)
311+
312+
_get_source_ir(_launch, self.x)
313+
assert seen == [(128, 4, 2)]
314+
315+
def test_sees_the_size_inferred_from_static_launch_dims(self):
316+
seen = []
317+
318+
@flyc.kernel
319+
def _kn(x: fx.Tensor):
320+
seen.append(fx.known_block_size())
321+
322+
@flyc.jit
323+
def _launch(x: fx.Tensor, stream: fx.Stream = fx.Stream(None)):
324+
_kn(x).launch(grid=(1, 1, 1), block=(256, 1, 1), stream=stream)
325+
326+
_get_source_ir(_launch, self.x)
327+
assert seen == [(256, 1, 1)]
328+
329+
def test_raises_for_a_dynamic_launch(self):
330+
@flyc.kernel
331+
def _kn(x: fx.Tensor, nthreads: fx.Int32):
332+
fx.known_block_size()
333+
334+
@flyc.jit
335+
def _launch(x: fx.Tensor, nthreads: fx.Int32):
336+
_kn(x, nthreads).launch(grid=(1, 1, 1), block=(nthreads, 1, 1))
337+
338+
with pytest.raises(RuntimeError, match="no compile-time block size"):
339+
_launch(self.x, 64)
340+
341+
def test_is_visible_from_a_nested_jit_helper(self):
342+
"""An inner tracing frame carries no size of its own and inherits the kernel's."""
343+
seen = []
344+
345+
@flyc.jit
346+
def _helper():
347+
seen.append(fx.known_block_size())
348+
349+
@flyc.kernel(known_block_size=[64, 1, 1])
350+
def _kn(x: fx.Tensor):
351+
_helper()
352+
353+
@flyc.jit
354+
def _launch(x: fx.Tensor, stream: fx.Stream = fx.Stream(None)):
355+
_kn(x).launch(grid=(1, 1, 1), block=(64, 1, 1), stream=stream)
356+
357+
_get_source_ir(_launch, self.x)
358+
assert seen == [(64, 1, 1)]

tests/unit/test_tracing_context.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# Copyright (c) 2026 FlyDSL Project Contributors
3+
4+
"""Tests for the ambient key/value options carried by ``tracing_context``."""
5+
6+
import pytest
7+
8+
from flydsl.expr.meta import tracing_context, tracing_option
9+
10+
pytestmark = pytest.mark.l0_backend_agnostic
11+
12+
13+
def _traced():
14+
pass
15+
16+
17+
def test_option_is_visible_inside_the_scope_only():
18+
assert tracing_option("fastmath") is None
19+
with tracing_context(_traced, fastmath="fast"):
20+
assert tracing_option("fastmath") == "fast"
21+
assert tracing_option("fastmath") is None
22+
23+
24+
def test_inner_scope_shadows_and_restores():
25+
with tracing_context(fastmath="fast", unroll=4):
26+
with tracing_context(fastmath="contract"):
27+
assert tracing_option("fastmath") == "contract"
28+
# Keys the inner frame does not set stay visible.
29+
assert tracing_option("unroll") == 4
30+
assert tracing_option("fastmath") == "fast"
31+
32+
33+
def test_explicit_none_shadows_an_outer_value():
34+
with tracing_context(fastmath="fast"):
35+
with tracing_context(_traced, fastmath=None):
36+
assert tracing_option("fastmath") is None
37+
assert tracing_option("fastmath") == "fast"
38+
39+
40+
def test_unset_key_inherits_the_outer_value():
41+
with tracing_context(fastmath="fast"):
42+
with tracing_context(_traced, unroll=4):
43+
assert tracing_option("fastmath") == "fast"
44+
45+
46+
def test_default_is_returned_for_unset_keys():
47+
with tracing_context(_traced):
48+
assert tracing_option("missing", "fallback") == "fallback"

0 commit comments

Comments
 (0)