Skip to content

Commit 003bba3

Browse files
committed
feat(mlir): collect kernel metadata and surface via PTX comments
After makeLLIR runs the Triton allocation passes, read the MLIR module attributes they write (ttg.shared, ttg.tensor_memory_size, ttg.global_scratch_memory_*, ttg.profile_scratch_memory_*, and ttg.total-num-warps) into a KernelMetadata struct on CudaBackend. In makeASM, store the kernel entry-point name then append all fields as structured `// meta:key=value` PTX line comments, which ptxas and the CUDA driver ignore. On the Rust side, add KernelMetadata with a parse() method that scans those comment lines, and call it in compile_module() so each MlirModule carries the parsed metadata alongside its PTX.
1 parent 6e7581d commit 003bba3

4 files changed

Lines changed: 115 additions & 39 deletions

File tree

compiler/rustc_codegen_llvm/src/mlir/backend.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,18 @@ fn compile_module(mlir_module: &mut MlirModule<'static>) -> Result<(), MlirError
206206
.to_owned();
207207

208208
eprintln!("Triton PTX output ({} bytes): {}", ptx.len(), ptx);
209+
210+
let metadata = crate::mlir::module::KernelMetadata::parse(&ptx);
211+
eprintln!(
212+
"Kernel metadata: name={:?} num_warps={} num_ctas={} shared={}B tmem={}B scratch={}B",
213+
metadata.name,
214+
metadata.num_warps,
215+
metadata.num_ctas,
216+
metadata.shared,
217+
metadata.tmem_size,
218+
metadata.global_scratch_size,
219+
);
220+
mlir_module.kernel_metadata = Some(metadata);
209221
mlir_module.ptx_asm = Some(ptx);
210222
Ok(())
211223
}

compiler/rustc_codegen_llvm/src/mlir/module.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,46 @@ use rustc_mlir::triton::TritonCompiler;
2525

2626
use crate::mlir::backend::MlirCodegenBackend;
2727

28+
/// Resource metadata for a compiled GPU kernel, recovered by parsing the
29+
/// structured `// meta:key=value` comments appended to the PTX by CudaBackend.
30+
#[derive(Debug, Default, Clone)]
31+
pub struct KernelMetadata {
32+
pub name: String,
33+
pub num_warps: i32,
34+
pub num_ctas: i32,
35+
pub shared: i32,
36+
pub tmem_size: i32,
37+
pub global_scratch_size: i32,
38+
pub global_scratch_align: i32,
39+
pub profile_scratch_size: i32,
40+
pub profile_scratch_align: i32,
41+
}
42+
43+
impl KernelMetadata {
44+
/// Parse `// meta:key=value` lines from PTX output.
45+
/// Lines that don't match the pattern are silently ignored.
46+
pub fn parse(ptx: &str) -> Self {
47+
let mut meta = KernelMetadata { num_ctas: 1, global_scratch_align: 1, profile_scratch_align: 1, ..Default::default() };
48+
for line in ptx.lines() {
49+
let Some(rest) = line.trim().strip_prefix("// meta:") else { continue };
50+
let Some((key, val)) = rest.split_once('=') else { continue };
51+
match key {
52+
"name" => meta.name = val.to_owned(),
53+
"num_warps" => meta.num_warps = val.parse().unwrap_or(0),
54+
"num_ctas" => meta.num_ctas = val.parse().unwrap_or(1),
55+
"shared" => meta.shared = val.parse().unwrap_or(0),
56+
"tmem_size" => meta.tmem_size = val.parse().unwrap_or(0),
57+
"global_scratch_size" => meta.global_scratch_size = val.parse().unwrap_or(0),
58+
"global_scratch_align" => meta.global_scratch_align = val.parse().unwrap_or(1),
59+
"profile_scratch_size" => meta.profile_scratch_size = val.parse().unwrap_or(0),
60+
"profile_scratch_align" => meta.profile_scratch_align= val.parse().unwrap_or(1),
61+
_ => {}
62+
}
63+
}
64+
meta
65+
}
66+
}
67+
2868
/// Represents an MLIR module during codegen
2969
pub struct MlirModule<'c> {
3070
pub name: String,
@@ -36,6 +76,8 @@ pub struct MlirModule<'c> {
3676
pub ptx_asm: Option<String>,
3777
/// MLIR source captured after cleanup passes, before Triton passes run.
3878
pub mlir_source: Option<String>,
79+
/// Kernel metadata parsed from the PTX comment block appended by CudaBackend.
80+
pub kernel_metadata: Option<KernelMetadata>,
3981
}
4082

4183
unsafe impl<'c> Send for MlirModule<'c> {}
@@ -57,7 +99,7 @@ impl<'c> MlirModule<'c> {
5799
let compiler = TritonCompiler::new(context.to_raw(), "cuda", &options)
58100
.expect("Failed to create Triton compiler");
59101

60-
Self { name: mod_name.to_string(), mlir: module, compiler, context, ptx_asm: None, mlir_source: None }
102+
Self { name: mod_name.to_string(), mlir: module, compiler, context, ptx_asm: None, mlir_source: None, kernel_metadata: None }
61103
}
62104

63105
pub fn context(&self) -> &Context {
@@ -84,6 +126,7 @@ impl<'c> MlirModule<'c> {
84126
compiler,
85127
ptx_asm: None,
86128
mlir_source: None,
129+
kernel_metadata: None,
87130
}
88131
}
89132

compiler/rustc_mlir/mlir-wrapper/triton/backend/CudaBackend.cpp

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ LogicalResult CudaBackend::makeASM(MLIRContext &context, ModuleOp module) {
219219
llvm::errs() << "Could not find kernel name in PTX output\n";
220220
return LogicalResult::failure();
221221
}
222+
m_metadata.name = kernel_name;
222223

223224
// 4. Post-process version and target
224225
char ptx_major_minor[8];
@@ -233,6 +234,20 @@ LogicalResult CudaBackend::makeASM(MLIRContext &context, ModuleOp module) {
233234
// No 'knobs' defined; always remove for now or leave as TODO.
234235
ret = std::regex_replace(ret, std::regex(R"(,\s*debug|debug,\s*)"), "");
235236

237+
// 6. Append kernel metadata as PTX line comments. PTX comments are ignored
238+
// by ptxas and the CUDA driver, so this is safe. The Rust side parses
239+
// these lines to recover launch parameters without a separate FFI channel.
240+
ret += "\n// --- triton-metadata ---\n";
241+
ret += "// meta:name=" + m_metadata.name + "\n";
242+
ret += "// meta:num_warps=" + std::to_string(m_metadata.num_warps) + "\n";
243+
ret += "// meta:num_ctas=" + std::to_string(m_metadata.num_ctas) + "\n";
244+
ret += "// meta:shared=" + std::to_string(m_metadata.shared) + "\n";
245+
ret += "// meta:tmem_size=" + std::to_string(m_metadata.tmem_size) + "\n";
246+
ret += "// meta:global_scratch_size=" + std::to_string(m_metadata.global_scratch_size) + "\n";
247+
ret += "// meta:global_scratch_align=" + std::to_string(m_metadata.global_scratch_align) + "\n";
248+
ret += "// meta:profile_scratch_size=" + std::to_string(m_metadata.profile_scratch_size) + "\n";
249+
ret += "// meta:profile_scratch_align=" + std::to_string(m_metadata.profile_scratch_align) + "\n";
250+
236251
// 7. Save PTX (exposed via getASM())
237252
m_asm = std::move(ret);
238253
return LogicalResult::success();
@@ -474,45 +489,30 @@ LogicalResult CudaBackend::makeLLIR(MLIRContext &context, ModuleOp module) {
474489
// CUDABackend.instrumentation.patch("llvmir_to_llvm", pm, mod.context)
475490
}
476491

477-
return pm.run(op);
492+
auto result = pm.run(op);
493+
if (succeeded(result)) {
494+
// Read resource metadata written by the allocation passes as MLIR module
495+
// attributes. These are later appended to the PTX as comments so Rust can
496+
// recover them without a separate FFI channel.
497+
auto getInt = [&](llvm::StringRef key, int32_t def = 0) -> int32_t {
498+
auto attr = op->getAttrOfType<mlir::IntegerAttr>(key);
499+
return attr ? static_cast<int32_t>(attr.getInt()) : def;
500+
};
478501

479-
// # LLVM-IR (MLIR) -> LLVM-IR (LLVM)
480-
// llvm.init_targets()
481-
// context = llvm.context()
482-
// if knobs.compilation.enable_asan:
483-
// raise RuntimeError(
484-
// "Address Sanitizer Error: Address sanitizer is currently only
485-
// supported on the AMD backend")
486-
// llvm_mod = llvm.to_module(mod, context)
487-
// proc = sm_arch_from_capability(capability)
488-
// features = get_features(options, self.target.arch)
489-
// triple = 'nvptx64-nvidia-cuda'
490-
// nvidia.set_short_ptr()
491-
// llvm.attach_datalayout(llvm_mod, triple, proc, features)
492-
// nvidia.set_nvvm_reflect_ftz(llvm_mod)
493-
494-
// if options.extern_libs and nvidia.has_extern_deps(llvm_mod):
495-
// paths = [path for (name, path) in options.extern_libs]
496-
// llvm.link_extern_libs(llvm_mod, paths)
497-
498-
// llvm.optimize_module(llvm_mod, llvm.OPTIMIZE_O3)
499-
500-
// # Get some metadata
501-
// # warp-specialization mutates num_warps
502-
// total_num_warps = src.get_int_attr("ttg.total-num-warps")
503-
// if total_num_warps is not None:
504-
// metadata["num_warps"] = total_num_warps
505-
// metadata["shared"] = src.get_int_attr("ttg.shared")
506-
// metadata["tmem_size"] = src.get_int_attr("ttg.tensor_memory_size")
507-
// metadata["global_scratch_size"] =
508-
// src.get_int_attr("ttg.global_scratch_memory_size")
509-
// metadata["global_scratch_align"] =
510-
// src.get_int_attr("ttg.global_scratch_memory_alignment")
511-
// metadata["profile_scratch_size"] =
512-
// src.get_int_attr("ttg.profile_scratch_memory_size") or 0
513-
// metadata["profile_scratch_align"] =
514-
// src.get_int_attr("ttg.profile_scratch_memory_alignment") or 1 ret =
515-
// str(llvm_mod) del llvm_mod del context return ret
502+
// Warp-specialization may have mutated the warp count, so prefer the
503+
// post-pipeline attribute over the original options value.
504+
auto totalWarps = op->getAttrOfType<mlir::IntegerAttr>("ttg.total-num-warps");
505+
m_metadata.num_warps = totalWarps ? static_cast<int32_t>(totalWarps.getInt())
506+
: m_options.num_warps;
507+
m_metadata.num_ctas = m_options.num_ctas;
508+
m_metadata.shared = getInt("ttg.shared");
509+
m_metadata.tmem_size = getInt("ttg.tensor_memory_size");
510+
m_metadata.global_scratch_size = getInt("ttg.global_scratch_memory_size");
511+
m_metadata.global_scratch_align = getInt("ttg.global_scratch_memory_alignment", 1);
512+
m_metadata.profile_scratch_size = getInt("ttg.profile_scratch_memory_size");
513+
m_metadata.profile_scratch_align = getInt("ttg.profile_scratch_memory_alignment", 1);
514+
}
515+
return result;
516516
}
517517

518518
std::unique_ptr<mlir::Pass>

compiler/rustc_mlir/mlir-wrapper/triton/backend/CudaBackend.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@ namespace triton {
3737

3838
namespace ttng = mlir::triton::nvidia_gpu;
3939

40+
// ---------------------------------------------------------------------------
41+
// Kernel metadata collected during lowering
42+
// ---------------------------------------------------------------------------
43+
44+
/// Resource metadata for a compiled GPU kernel. Populated by makeLLIR (from
45+
/// MLIR module attributes written by the allocation passes) and makeASM (kernel
46+
/// name extracted from PTX). Appended to m_asm as PTX line comments so that
47+
/// the Rust side can parse them without an extra FFI channel.
48+
struct KernelMetadata {
49+
std::string name;
50+
int32_t num_warps = 0;
51+
int32_t num_ctas = 1;
52+
int32_t shared = 0; // shared memory in bytes
53+
int32_t tmem_size = 0; // tensor memory (Blackwell SM100+)
54+
int32_t global_scratch_size = 0;
55+
int32_t global_scratch_align = 1;
56+
int32_t profile_scratch_size = 0;
57+
int32_t profile_scratch_align = 1;
58+
};
59+
4060
// ---------------------------------------------------------------------------
4161
// FFI-safe helper types
4262
// These map directly to #[repr(C)] Rust structs with equivalent fields.
@@ -216,6 +236,7 @@ class CudaBackend : public Backend {
216236

217237
CudaCompileOptions m_options;
218238
Capability m_capability;
239+
KernelMetadata m_metadata;
219240

220241
std::unordered_map<CudaPass, std::unique_ptr<Pass> (*)()> m_nvidia_pass_fns =
221242
{

0 commit comments

Comments
 (0)