tilelang.dataflow is a research prototype built on TileLang,
providing a CUDA-side programming and runtime path for dynamic, skewed workloads
with cross-SM dependencies. Its public APIs include
T.dataflow_program, while the implementation lives under
tilelang/dataflow/.
This guide explains how a declarative program becomes a CUDA kernel, how dynamic work is scheduled, and how cross-SM communication is implemented on NVIDIA GPUs.
Refer to examples/dataflow/mla/mla_decode_non_paged.py and examples/dataflow/fusedmoe/example_fusedmoe_dataflow.py for usage examples.
MoE results updated on 2026-09-10; MLA results retained from 2026-08-18.
Lower latency is better. "tilelang.dataflow speedup" is defined uniformly as:
competitor_p50 / tilelang_dataflow_p50 - 1
Measurements use NVIDIA H100 80GB HBM3 GPUs, sequentially, with one fresh Python process per point, one measured round, 5 warmups, and 30 samples. Observed active clocks were 1980 MHz SM and 2619 MHz HBM; PyTorch was 2.11.0+cu130 and NVCC was 13.0.88.
tilelang.dataflow used the generic automatically selected sm_count=112,
cluster_size=16 global-joint schedule at every point.
- tilelang.dataflow metric:
%globaltimerconcurrent global-span p50. - FlashMLA metric: same-host CUDA-event kernel-span p50.
- FlashMLA baseline revision:
9241ae3ef9bac614dd25e45e507e089f888280e0.
| Trace | Trace family | Batch | tilelang.dataflow p50 (us) | FlashMLA p50 (us) | tilelang.dataflow speedup |
|---|---|---|---|---|---|
| 0 | Dolphin-R1 | 16 | 43.296 | 53.488 | +23.540% |
| 1 | Dolphin-R1 | 32 | 64.304 | 72.016 | +11.993% |
| 2 | Dolphin-R1 | 64 | 105.424 | 114.048 | +8.180% |
| 3 | Dolphin-R1 | 128 | 185.680 | 201.840 | +8.703% |
| 4 | ShareGPT90K | 16 | 88.448 | 94.400 | +6.729% |
| 5 | ShareGPT90K | 32 | 44.048 | 54.032 | +22.666% |
| 6 | ShareGPT90K | 64 | 54.096 | 63.568 | +17.510% |
| 7 | ShareGPT90K | 128 | 92.240 | 103.728 | +12.454% |
| 8 | OpenR1-Math-220k | 16 | 71.408 | 78.368 | +9.747% |
| 9 | OpenR1-Math-220k | 32 | 120.592 | 128.048 | +6.183% |
| 10 | OpenR1-Math-220k | 64 | 293.312 | 306.896 | +4.631% |
| 11 | OpenR1-Math-220k | 128 | 573.504 | 596.032 | +3.928% |
| 12 | OpenThoughts-114k-Code | 16 | 59.696 | 68.720 | +15.117% |
| 13 | OpenThoughts-114k-Code | 32 | 99.392 | 108.160 | +8.822% |
| 14 | OpenThoughts-114k-Code | 64 | 213.104 | 226.864 | +6.457% |
| 15 | OpenThoughts-114k-Code | 128 | 409.520 | 430.848 | +5.208% |
Every tilelang.dataflow point passed reference correctness and two-launch bitwise determinism.
FP8, 32 experts, and top_k=2, with matching per-expert
token counts derived from EXPERT_HEAT in
benchmark.py:
- DeepSeek:
d_hidden=7168,d_expert=2048; - Qwen:
d_hidden=2048,d_expert=768.
The tilelang.dataflow scheduler selected topology, transport, and handoff through generic contracts for each workload. DeepSeek-1024 selected streamed producer push; the other seven points selected all-gather.
- MegaMoE metric: p50 of 30 freshly measured Kineto/CUPTI kernel durations.
- MegaMoE revision:
23f46aa68c892a349bb7ce331a325e36acceb57e.
| Model | Tokens | tilelang.dataflow p50 (us) | MegaMoE p50 (us) | tilelang.dataflow speedup |
|---|---|---|---|---|
| DeepSeek | 128 | 443.760 | 494.7935 | +11.500% |
| DeepSeek | 256 | 479.168 | 512.5215 | +6.961% |
| DeepSeek | 512 | 503.168 | 549.5450 | +9.217% |
| DeepSeek | 1024 | 550.752 | 574.1525 | +4.249% |
| Qwen | 128 | 74.960 | 86.5270 | +15.431% |
| Qwen | 256 | 79.504 | 87.0385 | +9.477% |
| Qwen | 512 | 77.312 | 94.3830 | +22.081% |
| Qwen | 1024 | 82.752 | 109.4865 | +32.307% |
tilelang.dataflow passed bitwise checks.
Modern inference operators receive work whose size and distribution depend on the current request. Decoding attention sees changing sequence lengths and batch composition. MoE sees changing token-to-expert routing, including hot experts and nearly empty experts. Skew within one batch makes both workloads hard to partition statically.
Conventional implementations split these operators into multiple kernels. MLA uses split-KV attention followed by reduction/finalization; MoE uses GEMM-1, GEMM-2, and output aggregation. Kernel boundaries provide correctness, but also turn imbalance into idle SM time, leave low-parallelism tail phases, move intermediate results through HBM, and add launch overhead.
tilelang.dataflow expresses the computation as a stage graph and executes its dependent subtasks through fine-grained SM cooperation.
The developer declares the logical task domain, dynamic range axes, operator bodies, and data dependencies. Concrete partitioning and placement are supplied by an input-dependent schedule rather than embedded in the algorithm.
The separation is visible in the execution path:
- Handlers contain statically compiled computation for map, reduce, and finalize operations.
- The plan describes the current task ranges, CTA placement, communication, storage, and handler variants.
- The wrapper kernel is a persistent dispatcher. Each CTA consumes its own instruction queue and invokes the requested handlers.
This model lets the same operator definition serve different sequence-length distributions or expert-routing results. The host-side kernel factory builds or retrieves an artifact specialized for the resulting plan.
When a producer and consumer CTA are in the same thread-block cluster, tilelang.dataflow can transfer an intermediate directly between their shared-memory address spaces. The intermediate does not need an HBM round trip. When the dependency crosses a cluster boundary, the planner uses an HBM-resident exchange slot with explicit device-scope synchronization.
The transport decision is part of the compiled plan, not the operator body. Operator code consumes a typed intermediate regardless of its physical route.
tilelang.dataflow refines a phase-level dependency into producer-consumer dependencies
between individual subtasks. A consumer waits for the slot it needs, while
other CTAs can continue independent work. Cluster transfers use hardware
mbarrier state; HBM transfers use flags and device-scope memory ordering.
Slot lifetime is planned together with synchronization. A producer cannot reuse a shared-memory source while a remote asynchronous read is still live, and a destination cannot be overwritten before its previous consumer releases it.
When several CTAs in a cluster need the same global-memory tile, a TMA multicast load can populate the selected shared-memory destinations with one issued operation. tilelang.dataflow carries multicast and cooperative-input choices through typed copy, layout, pipeline, and target-capability contracts so that common TileLang lowering can select a legal implementation.
The design targets NVIDIA GPUs with thread-block clusters and TMA. The cluster
communication templates are guarded for __CUDA_ARCH__ >= 900, so the mechanism
is not SM90-only: SM90 and SM100 are selected through target capability
resolution and architecture-specific lowering.
| Entity | Meaning |
|---|---|
| Task | One coordinate in the declared task domain, such as (batch, head_block) or group_block. |
| Dynamic range | Input-dependent work attached to a task, such as KV length, expert width, or output hidden width. |
| Stage | A node in the logical stage graph: map/partial, reshared, reduce, or finalize. |
| Handler | A statically lowered CUDA-callable implementation of a stage for a physical specialization. |
| Instruction | One scheduled handler invocation plus task, range, slot, and communication metadata. |
| Queue | The ordered instruction stream assigned to one logical CTA rank. |
| Slot | Planned storage for an intermediate in shared memory, scratch memory, or global staging memory. |
| Communication plan | A transfer and synchronization action connecting source and destination slots. |
| Wrapper | The cluster-launched kernel that drains queues, performs communication, and dispatches handlers. |
The logical and physical layers are deliberately separate:
declarative program
task domain + dynamic ranges + typed stages
|
v
input-dependent scheduling and specialization
CTA queues + ranges + handler variants + transport + lifetimes
|
v
ordinary TileLang PrimFuncs and operation contracts
|
v
common TileLang lowering and target selection
|
v
cluster wrapper + packed runtime plan + lowered handlers
The tilelang.dataflow layer owns scheduler facts such as instruction placement, handler arity, range specialization, and slot ownership. Common TileLang lowering owns the selection of concrete copy, GEMM, pipeline, and target instructions. This boundary prevents scheduler-specific facts from leaking into generic CUDA codegen.
- Capture the declarative graph. Decorated Python functions become operator
metadata and
OperatorCallobjects. Calling an operator while constructing a graph does not execute its body. - Validate logical contracts. The frontend checks stage dependencies, intermediate types, task arguments, dynamic range axes, and operation contracts.
- Resolve the target and topology. Compilation records a capability
snapshot for the selected GPU and a
GPUTopologydescribing SM and cluster organization. - Resolve dynamic work. The kernel factory turns current sequence lengths,
routed expert groups, or other range metadata into
range_lengths, optional offsets, task coordinates, and task weights. - Schedule subtasks. The scheduler partitions ranges, assigns work to CTA
queues, chooses reduction structure or stage-graph placement, and creates
InstructionPlanobjects. - Select handler variants. Range length, physical arity, reduction arity, precision, and execution contracts identify the required static handler variants.
- Plan storage and communication. The compiler assigns shared/HBM slots, proves lifetimes, chooses cluster or HBM transport, inserts barriers and release actions, and checks the target's shared-memory budget.
- Lower handlers. Operator bodies become ordinary PrimFuncs, then use the common TileLang transformation and CUDA lowering path. Typed operation contracts drive GEMM, copy, layout, and pipeline selection.
- Pack the runtime plan. Instructions, per-CTA queue offsets, handler arguments, slots, communication records, tensor arguments, and TMA descriptors are serialized into the runtime ABI.
- Generate and launch the wrapper. The wrapper initializes communication state, drains one queue per CTA, receives required inputs, invokes the selected handler, sends produced values, and observes slot-lifetime actions.
The public @df.jit path combines these steps with an artifact cache. Factory
arguments and the compile configuration contribute to the cache fingerprint.
A cache hit reuses the compiled plan and handlers; a new dynamic configuration
produces another plan/artifact. Once an artifact is launched, its queue layout
is immutable and the GPU wrapper follows that plan without making host-style
scheduling decisions inside the kernel.
Each launched CTA has a logical rank. The rank selects an offset and length in the flattened queue. The wrapper then repeatedly:
- loads the next
DataflowInstruction; - decodes task coordinates, dynamic range bounds, input slots, and output slot;
- issues or waits for receive-side communication;
- dispatches the instruction's handler variant;
- issues send-side communication and release actions;
- advances until
EXIT.
Receive-side work precedes the handler because its slots are handler inputs. Send-side work follows handler completion because its source slot is a handler output. Source and destination reuse waits are inserted according to the compiler's lifetime proof rather than after every operation.
Dynamic work does not require generating arbitrary CUDA code at device runtime. tilelang.dataflow uses a family of statically compiled variants:
- an exact-length variant for explicitly selected common lengths;
- a tile-count variant for common numbers of range tiles;
- a generic variant for all remaining lengths and tails.
iter_range_buckets="auto" currently denotes tile counts 1, 2, 4, plus
the generic case. iter_range_bucket_size defines the logical size of one tile.
The scheduler records the selected handler_variant_key in each instruction,
and the wrapper dispatches the matching handler. Generic handlers receive
range_begin, range_end, and dataflow_range_tiles_per_handler() and mask
partial tails.
This is why a short dynamic range can execute different code from a long range: the plan can bind a smaller unrolled/specialized handler instead of always running the maximum-range body. The generic handler remains the correctness fallback.
The following quantities must not be conflated:
block_sizeinDataflowKernelSpecis a scheduler range-partitioning unit. It is not necessarily the number of CUDA threads.threads=on@T.dataflow.map,@T.dataflow.iter,@T.dataflow.reduce, or@T.dataflow.finalizedeclares the operator's physical thread requirement.- Execution planning may choose
compute_threadsandconsumer_threadsfor a legal GEMM/pipeline implementation. - PrimFunc lowering resolves the physical thread count for every handler.
- The wrapper launch width is raised to the maximum resident handler thread requirement. A smaller handler runs with its declared thread limit inside that wrapper.
- One scheduled CTA occupies one cluster rank.
topology.cluster_sizecontrols the number of cooperating CTA ranks in a thread-block cluster.
Therefore, the programming abstraction does not infer CUDA thread count from a
logical map range alone. The operator's physical contract and selected target
implementation determine it, while the wrapper must be wide enough for every
handler it may dispatch.
| Concern | Source location |
|---|---|
| Program/stage graph and validation | tilelang/dataflow/program.py, DataflowProgram |
| Operator metadata and decorators | tilelang/dataflow/operators.py |
| Dynamic range buckets | tilelang/dataflow/iter_range_buckets.py |
| Scheduler IR and queue construction | tilelang/dataflow/scheduler.py, InstructionPlan |
| Automatic policy | tilelang/dataflow/scheduler_auto_policy.py |
| Execution-plan selection | tilelang/dataflow/execution_planning.py |
| Compiler orchestration | tilelang/dataflow/compiler.py, _compile_snapshot |
| Handler Body IR and PrimFunc lowering | tilelang/dataflow/body_ir.py, tilelang/dataflow/primfunc_lowering.py |
| Common lowering ownership contract | docs/dataflow/dataflow_refactor/mla_refactor/dataflow_lowering_architecture_contract.md |
| Runtime ABI packing | tilelang/dataflow/runtime.py, tilelang/dataflow/launch.py |
| Wrapper generation | tilelang/dataflow/wrapper.py |
| CUDA launch and target validation | tilelang/dataflow/executor.py |
User code normally imports both the TileLang language surface and tilelang.dataflow contracts:
import tilelang.language as T
import tilelang.dataflow as df@T.dataflow_intermediate declares the values crossing stage boundaries. A
field can be a scalar or a fixed physical tensor. Layout requests can be
attached when the common lowering path needs a matrix/swizzle contract.
@T.dataflow_intermediate
class Partial:
score_max: T.float32
score_sum: T.float32
output: T.Tensor((block_h, value_dim), T.float32)The type is used by graph validation, slot-size computation, handler ABI construction, memory planning, and communication planning. It is not only a Python annotation.
| Operator | Purpose |
|---|---|
@T.dataflow.iter |
Compute partial results over an input-dependent range. |
@T.dataflow.map |
Apply a map stage, optionally consuming and producing typed intermediates. |
DataflowProgram.reshared |
Preserve a logical intermediate while changing its physical partitioning, ownership, arity, or consumer order. |
@T.dataflow.reduce |
Associatively combine partial intermediates. |
@T.dataflow.finalize |
Convert the final intermediate into user-visible output. |
The former T.dataflow_iter, T.dataflow_map, T.dataflow_reduce, and
T.dataflow_finalize names remain compatibility aliases. The namespace also
leaves the existing T.reduce(buffer, out, ...) buffer-reduction API untouched.
partial(...) is the graph builder for a leading range-producing map stage.
It is used by the MLA partial -> reduce -> finalize pattern. General stage
graphs use map(...) and may insert reshared(...) between producer and
consumer maps.
Logical operators can carry typed requests that let the compiler choose a physical implementation without embedding scheduler or instruction details in the operator body.
| Contract | Controls |
|---|---|
DataflowRangeCoarseningRequest |
Logical range extent, handler extent, and output tile arity. |
DataflowPipelineRequest |
Producer/consumer synchronization and pipeline staging. |
DataflowOperatorPhysicalContract |
Input/output slot form, direct outputs, and physical slot expectations. |
DataflowResharedTransportRequest |
Reshared family, logical/physical arity, field mapping, and consumer order. |
DataflowCrossHandlerHandoffRequest |
Explicit resident buffering between successive handlers. |
DataflowTensorLayoutRequest |
Logical tensor rank and physical layout family. |
DataflowPrecisionPolicy |
Accumulator and specialization precision choices. |
DataflowMemoryPolicy |
Shared, scratch-backed, or direct-global memory planning policy. |
Contracts are legality and selection inputs. Hardware instruction selection still belongs to common TileLang lowering.
The non-paged MLA example declares:
program = (
T.dataflow_program(
task_domain=("batch", "head_block"),
dynamic_ranges={"kv": "seq_lens"},
)
.partial(split_tile(...), task_args=("batch", "head_block"), range_axis="kv")
.reduce(reduce_partial())
.finalize(finalize(Output="Output"), fused_reduce=reduce_finalizers)
)Execution proceeds as follows:
- Each
(batch, head_block)task obtains its own KV range length fromseq_lensand an optional range offset. - The scheduler divides the range into split-KV subtasks and balances them across CTA queues.
- Partial handlers produce online-softmax state and output accumulators.
- Reduction handlers combine only the available partials. Fine-grained producer-consumer edges replace a global phase boundary.
- The finalizer normalizes the accumulator and writes the output. Fused reduce finalizers remove avoidable tail dispatch when a legal reduction arity is known.
Short and long sequences can therefore use different subtask counts and handler variants within the same declarative operator.
The fused MoE example consumes grouped routing metadata and builds one logical
task per active token chunk (group_block):
program = (
T.dataflow_program(
task_domain=("group_block",),
dynamic_ranges={
"expert_tile": "expert_tiles",
"hidden_tile": "hidden_tiles",
},
)
.map(gemm1(), name="gemm1", range_axis="expert_tile", ...)
.reshared(input="gemm1", name="routed_reshared", transport_contract=...)
.map(gemm2(), input="routed_reshared", range_axis="hidden_tile", ...)
)The physical flow is:
- Routing metadata (
group_sizes, offsets, padded offsets, and the expert for each group block) determines active token chunks. A hot expert naturally contributes moregroup_blocktasks than a cold expert. - GEMM-1 maps the expert/intermediate dimension. Each handler loads the token
chunk and a shard of gate/up weights, computes the gated intermediate, and
produces
DataflowRoutedUpShardin a planned output slot. resharedchanges ownership from GEMM-1 expert shards to the input collection required by GEMM-2. The transport plan records physical arity, field-axis mapping, and GEMM-2 access order.- Within a cluster, the intermediate shards can be pushed directly to consumer shared-memory slots or kept resident when the execution contract permits it. Cross-cluster dependencies use planned HBM staging.
- GEMM-2 maps the output hidden dimension. For each hidden tile it consumes the logical GEMM-1 intermediate across expert shards, loads down-projection weights, accumulates, applies routing weights, and writes the output rows.
The scheduler can assign different amounts of work to CTA queues according to the active expert/token chunks. GEMM-1 and GEMM-2 remain separate handlers but execute under one wrapper launch, so their dependency does not require a kernel barrier or mandatory intermediate HBM round trip.
| Concern | Source location |
|---|---|
T.dataflow_* language exports |
tilelang/language/dataflow.py |
| Decorator implementation | tilelang/dataflow/operators.py |
| Intermediate schemas and calls | tilelang/dataflow/ir.py |
Program graph (map, reshared, reduce, finalize) |
tilelang/dataflow/program.py |
| Typed operation contracts | tilelang/dataflow/operation_contracts.py |
| Range coarsening | tilelang/dataflow/range_coarsening.py |
| Pipeline planning | tilelang/dataflow/pipeline_planning.py |
| Reshared transport planning | tilelang/dataflow/reshared_transport.py |
| Cross-handler handoff | tilelang/dataflow/handoff_planning.py, tilelang/dataflow/primfunc_lowering.py |
| GEMM implementation selection | tilelang/dataflow/gemm_lowering.py |
| MLA example | examples/dataflow/mla/mla_decode_non_paged.py |
| Fused MoE example | examples/dataflow/fusedmoe/example_fusedmoe_dataflow.py |
The host planner and CUDA wrapper share four central records:
| Record | Selected contents |
|---|---|
DataflowInstruction |
Opcode, handler ID, task ID, argument offset, communication range, primary slot, flags. |
DataflowQueue |
Flat instruction array plus per-CTA offsets and lengths. |
DataflowSlot |
Shared/global offsets, byte size, storage flags, and lifetime metadata. |
DataflowCommPlan |
Transport kind, source/destination slots, peer CTA rank, bytes, flag epoch, and barrier metadata. |
Fixed-width layouts are mirrored by Python packing in
tilelang/dataflow/runtime.py and CUDA structs in
src/tl_templates/cuda/dataflow_runtime.h. ABI size assertions in the CUDA
header protect host/device compatibility.
For a same-cluster edge, the transfer protocol is:
- The destination CTA initializes a local
mbarrierfor the destination slot. - The producer maps the destination shared-memory pointer and barrier into the
cluster address space with
mapa.shared::cluster. - The producer performs remote
mbarrier.arrive.expect_txwith the expected byte count, establishes the asynchronous shared-memory proxy ordering, and issuescp.async.bulk.shared::cluster.shared::ctawithmbarrier::complete_tx::bytes. - The
complete_tx::bytesform binds copy completion to the destination barrier. Hardware retires the pending transaction bytes as the asynchronous copy completes. - The consumer waits with the hardware
mbarrierparity-wait helper and then executes the required proxy fence before ordinary shared-memory reads.
Destination completion and source lifetime are related but distinct. The
destination mbarrier tells the consumer when the received bytes are visible.
The wrapper also tracks completion/acknowledgement for outstanding remote reads
so that the producer cannot reuse its source slot or exit while a cluster send
still references it.
This protocol is asynchronous at the copy engine. It does not mean that the producer may immediately destroy the source or that the consumer may read before the barrier completes.
Cluster shared memory is addressable only within a thread-block cluster. For a cross-cluster dependency, tilelang.dataflow uses a global exchange slot:
- the producer writes the slot through a TMA or scalar global path;
- a device-scope fence orders the payload before a release flag;
- the consumer waits for the corresponding flag epoch with acquire semantics;
- the consumer issues a global-to-shared TMA load and waits on its local
mbarrier, or uses the scalar fallback selected by the plan; - flag epochs and slot lifetimes allow safe reuse across queue generations.
The logical handler ABI is unchanged. Only the slot and communication records differ from the cluster-shared route.
TMA multicast is a global-to-cluster-shared operation. The issued instruction
contains a CTA mask identifying cluster ranks that receive the tensor tile. Each
participating destination associates the transfer with its mbarrier, and
consumers wait before using the shared-memory tile.
At the TileLang surface this appears as a copy with multicast/cooperative input annotations and a compatible tensor-map descriptor. Lowering validates layout, alignment, cluster shape, and target capabilities before emitting the SM90+ TMA form. This path is useful when MLA CTAs share query-side data or when several MoE GEMM shards consume the same token tile or weight tile.
Multicast is different from unicast: unicast has one destination CTA per issued transfer, while multicast names multiple destination ranks in the cluster.
CUDA exposes cluster launch controls but does not directly provide the complete physical GPC grouping needed by the scheduler. tilelang.dataflow measures cluster occupancy behavior and observed SM IDs, then infers a usable topology model. The scheduler uses that model to form legal clusters, keep communicating CTAs close, and avoid fragmenting the available SM set.
The resolved topology and target capability snapshot are included in compile and artifact fingerprints. A launch validates the artifact against the actual device before loading the generated kernel.
| Concern | Source location |
|---|---|
| Host-side ABI records and packing | tilelang/dataflow/runtime.py |
| Launch package and uploaded buffers | tilelang/dataflow/launch.py, tilelang/dataflow/executor.py |
| Device ABI and queue helpers | src/tl_templates/cuda/dataflow_runtime.h |
| Cluster/HBM communication dispatch | src/tl_templates/cuda/dataflow_comm.h |
| Cluster TMA store and TMA multicast templates | src/tl_templates/cuda/copy_sm90.h |
| Tile copy lowering | src/backend/cuda/op/copy.cc, src/transform/lower_tile_op.cc |
| CUDA wrapper dispatch generation | tilelang/dataflow/wrapper.py, tilelang/dataflow/primfunc_linking.py |
| Barrier and slot lifetime planning | tilelang/dataflow/barrier_planning.py, tilelang/dataflow/memory_planner.py |
| GPC/SM topology probe | tilelang/dataflow/gpc_sm_probe.py, tilelang/dataflow/die_sm_probe.py |
| Topology model | tilelang/dataflow/hardware_topology.py, tilelang/dataflow/topology.py |
| Target capability checks | tilelang/utils/target_capabilities.py |
- Identify the logical task domain and the input-dependent range axes.
- Declare every cross-stage value as a typed
dataflow_intermediate. - Write map/iter, reduce, and finalize bodies as ordinary TileLang operator bodies.
- Compose the stage graph and name dependencies explicitly.
- Add typed range, pipeline, layout, transport, handoff, precision, and memory requests only where the operator requires them.
- Build a
DataflowKernelSpecwith topology, current range lengths, task extents/coordinates,block_size, and scheduler configuration. - Compile through
@df.jitordf.compile_kernel_specand inspect the resulting plan before benchmarking.
DataflowCompiledProgram exposes structured information intended for tooling
and maintenance:
compiled = kernel_factory(...)
plan = compiled.dump_plan()
decisions = compiled.decision_artifact()
layout_report = compiled.validate_memory_layout()
cuda_source = compiled.wrapper_sourcedump_plan()shows queue lengths, instructions, range specialization, reshared transport, handoff, slots, communication, and launch resources.decision_artifact()records implementation selection and target provenance.validate_memory_layout()checks handler scratch, communication slots, and dynamic shared-memory layout.wrapper_sourceis useful for debugging generated code; structured artifacts are the stable inspection boundary.
| Change | Primary files |
|---|---|
| Add or validate a frontend construct | tilelang/language/dataflow.py, tilelang/dataflow/operators.py, tilelang/dataflow/program.py |
| Add a scheduling policy | tilelang/dataflow/scheduler.py, tilelang/dataflow/scheduler_auto_policy.py, tilelang/dataflow/scheduler_config.py |
| Add a handler specialization | tilelang/dataflow/handler_identity.py, tilelang/dataflow/handler_registry.py, tilelang/dataflow/primfunc_lowering.py |
| Add a physical operation contract | tilelang/dataflow/operation_contracts.py and the matching planner/lowering module |
| Add a transport realization | tilelang/dataflow/reshared_transport.py, tilelang/dataflow/runtime.py, src/tl_templates/cuda/dataflow_comm.h |
| Change wrapper dispatch or lifetime handling | tilelang/dataflow/wrapper.py, tilelang/dataflow/primfunc_linking.py, tilelang/dataflow/barrier_planning.py |
| Add a generic TileLang hardware implementation | src/backend/cuda/op/, src/transform/, and src/tl_templates/cuda/ |
| Add an end-to-end operator | examples/dataflow/ plus focused tests under testing/python/dataflow/ |
The following groups cover the main contracts:
- Frontend and graph:
test_dataflow_frontend.py,test_dataflow_program.py,test_dataflow_reshared_graph.py. - Scheduling and specialization:
test_dataflow_scheduler.py,test_dataflow_scheduler_auto_policy.py,test_dataflow_range_coarsening.py. - Lowering and wrapper:
test_dataflow_primfunc_lowering.py,test_dataflow_primfunc_linking.py,test_dataflow_wrapper_compile.py. - Runtime and communication:
test_dataflow_runtime.py,test_dataflow_barrier_planning.py,test_dataflow_cluster_mbarrier_debug.py. - Operators:
test_dataflow_mla_decode_nonpaged.pyandtest_dataflow_fusedmoe_example.py. - Target and topology:
test_dataflow_target_capabilities.pyandtest_dataflow_topology_probe.py.
All test files above are under testing/python/dataflow/.
tilelang.dataflow turns a multi-phase inference operator into a typed graph of dynamic subtasks. Static handlers implement computation; an input-dependent plan assigns those handlers to per-CTA queues, selects shape variants, and plans storage and communication. A cluster wrapper executes the queues in one launch.
For MLA, this model balances split-KV work and performs fine-grained reduction
and finalization. For MoE, it connects GEMM-1 and GEMM-2 through a reshared
intermediate whose physical ownership is chosen for the routed token workload.
In both cases, same-cluster dependencies can use TMA and hardware mbarrier
instead of mandatory HBM round trips and kernel barriers, while target-aware HBM
staging preserves the same logical execution model across cluster boundaries.
For the detailed design, please refer to our SOSP '26 paper:
Taming Dynamism on GPUs: Cross-SM Kernel Fusion via SM Cooperation and Just-in-Time Reduction
Jingkai He, Guangda Sun, Tianjian Li, Dong Du, Yubin Xia, Haibo Chen (Shanghai Jiao Tong University)
This project is still undergoing further refactoring:
- Integrate TMA multicast into the general scheduling path to accelerate
scenarios such as shared-prefix loading in attention. This capability is
currently available through
copy_cluster. - Complete comprehensive testing on NVIDIA Blackwell GPUs.