Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ opener = "0.6.1"
regex = "1.9.2"
serde = { version = "1.0.185", features = ["serde_derive"] }
serde_json = "1.0.100"
similar = "2"
tinytemplate = "1.1.0"
tiny_http = "0.12"

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ pub fn parse_path(path: &PathBuf, config: &ParseConfig) -> anyhow::Result<ParseO
}
tt.add_template("provenance_tracking.html", TEMPLATE_PROVENANCE_TRACKING)?;
tt.add_template("vllm_summary.html", vllm::templates::VLLM_SUMMARY_TEMPLATE)?;
tt.add_template("vllm_diff.html", vllm::templates::VLLM_DIFF_TEMPLATE)?;

let mut unknown_fields: FxHashSet<String> = FxHashSet::default();

Expand Down
231 changes: 230 additions & 1 deletion src/vllm/parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ use crate::templates::TEMPLATE_QUERY_PARAM_SCRIPT;
use crate::types::{CompileId, Envelope};

use super::types::{
ArtifactInfo, VllmCompilationConfig, VllmCompileRangeGroup, VllmSubgraphInfo,
ArtifactInfo, VllmCompilationConfig, VllmCompileRangeGroup, VllmDiffContext, VllmSubgraphInfo,
VllmSubgraphWithArtifacts, VllmSummaryContext,
};

use std::cell::RefCell;
use std::fmt::Write as FmtWrite;
use std::rc::Rc;
use tinytemplate::TinyTemplate;

Expand Down Expand Up @@ -250,11 +251,239 @@ impl StructuredLogParser for VllmPiecewiseSplitGraphParser {
}
}

// Parses two kinds of log entries to produce per-pass diff pages:
//
// 1. "before_post_grad_graph" artifact — the graph before any passes run.
// Stored as the diff baseline; no file output (ArtifactParser handles that).
//
// 2. "vllm_post_grad.<index>.<PassName>" graph dump — the graph after a pass.
// Diffed against `previous_payload` to produce a side-by-side HTML diff,
// then becomes the new baseline for the next pass.
pub struct VllmPostGradPassDiffParser {
state: Rc<VllmState>,
// The graph payload from the previous pass (or before_post_grad_graph),
// used as the "before" side of the next diff.
previous_payload: RefCell<Option<String>>,
}

impl VllmPostGradPassDiffParser {
pub fn new(state: Rc<VllmState>) -> Self {
Self {
state,
previous_payload: RefCell::new(None),
}
}

// Build a side-by-side diff HTML table from two text payloads.
//
// Uses `similar` to compute a unified diff, then renders each hunk as a
// 4-column table: [old line num | old code | new line num | new code].
//
// For changed regions, consecutive Delete and Insert lines are collected
// and paired row-by-row so additions appear next to the deletions they
// replaced (like GitHub's side-by-side view).
fn generate_diff_html(before: &str, after: &str) -> String {
use similar::{ChangeTag, TextDiff};

let diff = TextDiff::from_lines(before, after);

let mut html = String::new();
let mut has_hunks = false;

for hunk in diff.unified_diff().context_radius(3).iter_hunks() {
// Emit table header on first hunk
if !has_hunks {
html.push_str("<div class=\"diff-wrapper\"><table class=\"diff-table\">\n<colgroup><col class=\"line-num-col\"><col class=\"code-col\"><col class=\"line-num-col\"><col class=\"code-col\"></colgroup>\n");
has_hunks = true;
}

let _ = write!(
html,
"<tr class=\"diff-hunk\"><td colspan=\"4\">@@ {} @@</td></tr>\n",
html_escape::encode_text(&hunk.header().to_string()),
);

// Walk changes, grouping consecutive deletes+inserts for side-by-side pairing
let changes: Vec<_> = hunk.iter_changes().collect();
let mut i = 0;
while i < changes.len() {
match changes[i].tag() {
// Unchanged context line — show on both sides
ChangeTag::Equal => {
let text =
html_escape::encode_text(changes[i].value().trim_end_matches('\n'));
let old_line = changes[i].old_index().map(|n| n + 1);
let new_line = changes[i].new_index().map(|n| n + 1);
let _ = write!(
html,
"<tr><td class=\"line-num\">{}</td><td class=\"code-cell\">{}</td><td class=\"line-num right-num\">{}</td><td class=\"code-cell\">{}</td></tr>\n",
old_line.map(|n| n.to_string()).unwrap_or_default(),
text,
new_line.map(|n| n.to_string()).unwrap_or_default(),
text,
);
i += 1;
}
// Deletion — collect consecutive deletes, then consecutive inserts,
// and pair them row-by-row (excess on either side gets blank cells)
ChangeTag::Delete => {
let mut deletes = Vec::new();
while i < changes.len() && changes[i].tag() == ChangeTag::Delete {
deletes.push(&changes[i]);
i += 1;
}
let mut inserts = Vec::new();
while i < changes.len() && changes[i].tag() == ChangeTag::Insert {
inserts.push(&changes[i]);
i += 1;
}
let max_len = deletes.len().max(inserts.len());
for j in 0..max_len {
let (left_class, left_num, left_text) = if j < deletes.len() {
let num = deletes[j].old_index().map(|n| n + 1);
let text = html_escape::encode_text(
deletes[j].value().trim_end_matches('\n'),
);
(" diff-del", num, text.to_string())
} else {
("", None, String::new())
};
let (right_class, right_num, right_text) = if j < inserts.len() {
let num = inserts[j].new_index().map(|n| n + 1);
let text = html_escape::encode_text(
inserts[j].value().trim_end_matches('\n'),
);
(" diff-add", num, text.to_string())
} else {
("", None, String::new())
};
let _ = write!(
html,
"<tr><td class=\"line-num\">{}</td><td class=\"code-cell{}\">{}</td><td class=\"line-num right-num\">{}</td><td class=\"code-cell{}\">{}</td></tr>\n",
left_num.map(|n| n.to_string()).unwrap_or_default(),
left_class,
left_text,
right_num.map(|n| n.to_string()).unwrap_or_default(),
right_class,
right_text,
);
}
}
// Standalone insert (not preceded by a delete)
ChangeTag::Insert => {
let text =
html_escape::encode_text(changes[i].value().trim_end_matches('\n'));
let new_line = changes[i].new_index().map(|n| n + 1);
let _ = write!(
html,
"<tr><td class=\"line-num\"></td><td class=\"code-cell\"></td><td class=\"line-num right-num\">{}</td><td class=\"code-cell diff-add\">{}</td></tr>\n",
new_line.map(|n| n.to_string()).unwrap_or_default(),
text,
);
i += 1;
}
}
}
}

if has_hunks {
html.push_str("</table></div>\n");
} else {
html = "<div class=\"no-changes\">(no changes)</div>\n".to_string();
}

html
}
}

impl StructuredLogParser for VllmPostGradPassDiffParser {
fn name(&self) -> &'static str {
"vllm_post_grad_pass_diff"
}

fn get_metadata<'e>(&self, e: &'e Envelope) -> Option<Metadata<'e>> {
if let Some(graph_dump) = &e.graph_dump {
if graph_dump.name.starts_with("vllm_post_grad.") {
return Some(Metadata::GraphDump(graph_dump));
}
}
if let Some(artifact) = &e.artifact {
if artifact.name == "before_post_grad_graph" {
return Some(Metadata::Artifact(artifact));
}
}
None
}

fn parse<'e>(
&self,
lineno: usize,
metadata: Metadata<'e>,
_rank: Option<u32>,
compile_id: &Option<CompileId>,
payload: &str,
) -> anyhow::Result<ParserResults> {
// before_post_grad_graph (artifact): seed baseline for first pass diff.
// Don't output a file — the default ArtifactParser handles that.
if matches!(metadata, Metadata::Artifact(a) if a.name == "before_post_grad_graph") {
*self.previous_payload.borrow_mut() = Some(payload.to_string());
return Ok(Vec::new());
}

let graph_dump = match metadata {
Metadata::GraphDump(gd) => gd,
_ => return Ok(Vec::new()),
};

*self.state.has_vllm_artifacts.borrow_mut() = true;

// e.g. "vllm_post_grad.0.FusionPass" -> pass_name = "0.FusionPass"
let pass_name = graph_dump
.name
.strip_prefix("vllm_post_grad.")
.unwrap_or(&graph_dump.name);

// Always output the raw post-pass graph as a .txt file
let txt_filename = format!("{}.txt", graph_dump.name);
let txt_path = build_file_path(&txt_filename, lineno, compile_id);
let mut results: ParserResults = vec![ParserOutput::PayloadFile(txt_path)];

// If we have a baseline, generate a side-by-side diff page
let prev = self.previous_payload.borrow().clone();
if let Some(before) = prev {
let diff_html = Self::generate_diff_html(&before, payload);

let context = VllmDiffContext {
css: super::templates::VLLM_CSS.to_string(),
pass_name: pass_name.to_string(),
diff_html,
qps: TEMPLATE_QUERY_PARAM_SCRIPT.to_string(),
};

let mut tt = TinyTemplate::new();
tt.add_formatter("format_unescaped", tinytemplate::format_unescaped);
tt.add_template("vllm_diff.html", super::templates::VLLM_DIFF_TEMPLATE)?;
let rendered = tt.render("vllm_diff.html", &context)?;

let diff_filename = format!("vllm_post_grad.{}.diff.html", pass_name);
let diff_path = build_file_path(&diff_filename, lineno, compile_id);

results.push(ParserOutput::GlobalFile(diff_path, rendered));
}

// This pass's output becomes the baseline for the next pass's diff
*self.previous_payload.borrow_mut() = Some(payload.to_string());

Ok(results)
}
}

pub fn vllm_parsers_with_state(state: Rc<VllmState>) -> Vec<Box<dyn StructuredLogParser>> {
vec![
Box::new(VllmCompilationConfigParser::new(state.clone())),
Box::new(VllmPiecewiseSplitGraphParser::new(state.clone())),
Box::new(VllmPiecewiseCompileParser::new(state.clone())),
Box::new(VllmPostGradPassDiffParser::new(state.clone())),
]
}

Expand Down
74 changes: 74 additions & 0 deletions src/vllm/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,80 @@ h3 {
}
"#;

pub const VLLM_DIFF_TEMPLATE: &str = r#"<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Pass Diff: {pass_name}</title>
<style>
{css | format_unescaped}
.diff-wrapper \{
overflow-x: auto;
margin: 15px 0;
border: 1px solid #d0d7de;
border-radius: 5px;
}
.diff-table \{
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 13px;
line-height: 1.4;
border-collapse: collapse;
table-layout: fixed;
width: 100%;
background: white;
}
.diff-table col.line-num-col \{
width: 40px;
}
.diff-table col.code-col \{
width: calc(50% - 40px);
}
.diff-table td \{
white-space: pre-wrap;
word-break: break-all;
padding: 1px 8px;
vertical-align: top;
}
.diff-table .line-num \{
white-space: nowrap;
text-align: right;
color: #8b949e;
padding: 1px 4px;
user-select: none;
overflow: visible;
}
.diff-table .right-num \{
border-left: 1px solid #d0d7de;
}
.diff-add \{
background: #e6ffec;
}
.diff-del \{
background: #ffebe9;
}
.diff-hunk \{
background: #ddf4ff;
}
.diff-hunk td \{
color: #0969da;
font-weight: bold;
padding: 5px 8px;
}
.no-changes \{
padding: 20px;
text-align: center;
color: #57606a;
}
</style>
</head>
<body>
<h1>Pass Diff: {pass_name}</h1>
{diff_html | format_unescaped}
{qps | format_unescaped}
</body>
</html>
"#;

pub const VLLM_SUMMARY_TEMPLATE: &str = r#"<!DOCTYPE html>
<html>
<head>
Expand Down
8 changes: 8 additions & 0 deletions src/vllm/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ pub struct VllmSummaryContext {
pub compile_range_groups: Vec<VllmCompileRangeGroup>,
}

#[derive(Debug, Serialize)]
pub struct VllmDiffContext {
pub css: String,
pub pass_name: String,
pub diff_html: String,
pub qps: String,
}

#[derive(Debug, Clone, Serialize)]
pub struct VllmSubgraphWithArtifacts {
pub submod_name: String,
Expand Down
Loading
Loading