mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 10:33:59 -05:00
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>
202 lines
7.8 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|