Files
compiler-explorer/lib/compilers/triton.ts
Shawn Zhong 8befc91a79 Add Triton language and compiler (#7919)
Close #5530. Infra:
https://github.com/compiler-explorer/infra/pull/1711. Previous work by
@siboehm at #5531


## Summary

This pull request introduces support for the
[Triton](https://github.com/triton-lang/triton) language, a Python-based
DSL for writing highly efficient GPU kernels.


- [x] **New Language Support**: I've added comprehensive support for the
Triton programming language, allowing users to compile and inspect
Triton kernels within Compiler Explorer. (c.f.,
`lib/compilers/triton.ts`)
- [x] **Python Wrapper for Compilation**: A new Python wrapper script
(`triton_wrapper.py`) has been introduced to manage Triton compilation,
patching its behavior to dump compiled kernels and intermediate
representations without requiring actual execution, and consolidating
the output for Compiler Explorer.
- [x] **Device Assembly View**: Enables viewing of generated device
assembly code (e.g., PTX, AMDGCN) and various intermediate
representations (MLIR, LLVM IR) produced by the Triton compiler.
- [x] **MLIR Parsing**: New parsers (`asm-parser-mlir.ts` and
`mlir-pass-dump-parser.ts`) have been added to correctly interpret and
display MLIR assembly and optimization pass dumps, including source
location information.
- [x] **Multi-Version & Multi-Backend Support**: Painstakingly includes
all 8 versions (from 2.2.0 to 3.3.1) of Triton that supports Python
3.12. Supports both CUDA and HIP backend for Triton 3.

## Screenshots

Source and assembly:
<img width="1354" height="789" alt="image"
src="https://github.com/user-attachments/assets/c29650ff-2073-40e0-a9e6-ff8377094b5e"
/>



Device view for MLIR and LLVM IR: 
<img width="1402" height="670" alt="image"
src="https://github.com/user-attachments/assets/43dd5c68-ca78-41b1-9865-e97ffe3ef73c"
/>

Opt pipeline viewer:
<img width="1408" height="668" alt="image"
src="https://github.com/user-attachments/assets/429eef8c-aaac-4781-aafa-39ef0ffc7241"
/>

Diff of TTIR in Triton 3.3.1 vs 2.3.0:
<img width="1580" height="726" alt="image"
src="https://github.com/user-attachments/assets/a928c893-dd9a-4c3a-a048-14046e56a14c"
/>

CUDA & HIP:
<img width="1596" height="800" alt="image"
src="https://github.com/user-attachments/assets/c18800c3-cfad-4e5e-96de-ba92c9f236ea"
/>

## Implementation Details (and Notes for Reviewers)

- For Device Assembly View, I Implemented `MlirAsmParser` for parsing
MLIR assembly. Technically MLIR is not an assembly language, but there
is no better choice to make the source line map work w/ device view.
- I Implemented `MlirPassDumpParser` for processing MLIR optimization
pass dumps. I tried to subclass `LlvmPassDumpParser`, but they turn out
to be too different to worth doing it.
- `LlvmPassDumpParser` made some assumptions that do not hold true for
MLIR passed. Some effort is put to make sure that the passes are
properly diff-ed, since some passes can run multiple times and also
sometimes pass can be nested (i.e., some number of `before`s followed by
some number of `after`s)
- A lot of effort is put into `patch_triton` to make sure that the we
only compile the kernel without actually running it, and that needs to
work across all the versions we support.

## Steps to Run Locally

1. Clone https://github.com/ShawnZhong/compiler-explorer-infra.git
2. Install Triton to `/opt/compiler-explorer/triton`:
```sh
$ cd compiler-explorer-infra
$ ./bin/ce_install install triton
$ ls /opt/compiler-explorer/triton
# v2.2.0  v2.3.0  v2.3.1  v3.0.0  v3.1.0  v3.2.0  v3.3.0  v3.3.1
```
3. Clone https://github.com/ShawnZhong/compiler-explorer.git and
checkout branch `triton`
4. Run Compiler Explorer
```sh
make EXTRA_ARGS='--language triton' dev
```
5. Enjoy

---------

Co-authored-by: Matt Godbolt <matt@godbolt.org>
2025-07-30 10:15:28 -05:00

202 lines
7.8 KiB
TypeScript

// Copyright (c) 2025, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import * as fs from 'node:fs/promises';
import Path from 'node:path';
import type {CompilationInfo, CompilationResult} from '../../types/compilation/compilation.interfaces.js';
import type {
OptPipelineBackendOptions,
OptPipelineOutput,
} from '../../types/compilation/opt-pipeline-output.interfaces.js';
import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
import {BaseCompiler} from '../base-compiler.js';
import {CompilationEnvironment} from '../compilation-env.js';
import type {IAsmParser} from '../parsers/asm-parser.interfaces.js';
import {AmdgpuAsmParser} from '../parsers/asm-parser-amdgpu.js';
import {MlirAsmParser} from '../parsers/asm-parser-mlir.js';
import {PTXAsmParser} from '../parsers/asm-parser-ptx.js';
import {SassAsmParser} from '../parsers/asm-parser-sass.js';
import {MlirPassDumpParser} from '../parsers/mlir-pass-dump-parser.js';
import {parseOutput, resolvePathFromAppRoot} from '../utils.js';
import {BaseParser} from './argument-parsers.js';
export class TritonCompiler extends BaseCompiler {
private compilerWrapperPath: string;
static get key() {
return 'triton';
}
parserMap: Record<string, IAsmParser>;
mlirPassDumpParser: MlirPassDumpParser;
constructor(compilerInfo: PreliminaryCompilerInfo, env: CompilationEnvironment) {
super(compilerInfo, env);
this.compilerWrapperPath =
this.compilerProps('compilerWrapper', '') || resolvePathFromAppRoot('etc', 'scripts', 'triton_wrapper.py');
// Enable the Opt Pipeline view
this.compiler.optPipeline = {};
// Used to parse the output of the opt pipeline
this.mlirPassDumpParser = new MlirPassDumpParser(this.compilerProps);
// Enable the Device Viewer
this.compiler.supportsDeviceAsmView = true;
// Define parsers for the different output files displayed in the Device Viewer
const sassAsmParser = new SassAsmParser(this.compilerProps);
const ptxAsmParser = new PTXAsmParser(this.compilerProps);
const amdgpuAsmParser = new AmdgpuAsmParser();
const mlirAsmParser = new MlirAsmParser();
this.parserMap = {
'.ttir': mlirAsmParser,
'.ttgir': mlirAsmParser,
'.ptx': ptxAsmParser,
'.sass': sassAsmParser,
'.source': mlirAsmParser,
'.amdgcn': amdgpuAsmParser,
'.llir': mlirAsmParser,
'.json': sassAsmParser,
};
if (compilerInfo.group == 'triton_amd') {
this.asm = amdgpuAsmParser;
} else if (compilerInfo.group == 'triton_nvidia') {
this.asm = ptxAsmParser;
}
}
override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string): string[] {
// See etc/scripts/triton_wrapper.py for the options
return ['-I', this.compilerWrapperPath, '--output_file', outputFilename];
}
override getArgumentParserClass() {
return BaseParser;
}
override async extractDeviceCode(
result: CompilationResult,
filters: ParseFiltersAndOutputOptions,
compilationInfo: CompilationInfo,
) {
const devices = {...result.devices};
const {dirPath} = result;
if (!dirPath) {
return result;
}
// Extract the device code from the output directory
const files = await fs.readdir(dirPath);
await Promise.all(
files.map(async filename => {
const ext = Path.extname(filename);
const parser = this.parserMap[ext];
if (!parser) {
return;
}
// Read the file
const data = await fs.readFile(Path.join(dirPath, filename), 'utf8');
// Parse the assembly with line numbers
let device;
if (ext === '.llir') {
device = await this.llvmIr.process(data, {
filterDebugInfo: false,
filterIRMetadata: false,
filterAttributes: false,
filterComments: false,
noDiscardValueNames: false,
demangle: false,
});
} else {
device = await parser.process(data, filters);
}
Object.assign(devices, {[filename]: device});
}),
);
result.devices = devices;
return result;
}
override async generateOptPipeline(
inputFilename: string,
options: string[],
filters: ParseFiltersAndOutputOptions,
optPipelineOptions: OptPipelineBackendOptions,
): Promise<OptPipelineOutput | undefined> {
// Call the script to generate the opt pipeline
const execOptions = this.getDefaultExecOptions();
const outputFilename = Path.join(Path.dirname(inputFilename), 'opt_pipeline.txt');
const optOptions = [...options, '--opt_pipeline_file', outputFilename];
const compileStart = performance.now();
await this.runCompiler(this.compiler.exe, optOptions, inputFilename, execOptions);
const compileEnd = performance.now();
// Read the output file and parse it
try {
const rawText = await fs.readFile(outputFilename, {encoding: 'utf8'});
const lines = parseOutput(rawText);
const parseStart = performance.now();
const llvmOptPipeline = await this.mlirPassDumpParser.process(lines, filters, optPipelineOptions);
const parseEnd = performance.now();
return {
results: llvmOptPipeline,
compileTime: compileEnd - compileStart,
parseTime: parseEnd - parseStart,
};
} catch (e: any) {
return {
error: e.toString(),
results: {},
compileTime: compileEnd - compileStart,
};
}
}
override getDefaultFilters() {
return {
intel: false,
commentOnly: false,
directives: false,
labels: false,
optOutput: true,
binary: false,
execute: false,
demangle: false,
libraryCode: false,
trim: false,
binaryObject: false,
debugCalls: false,
};
}
}