Skip to content

Add --all-ranks CLI infrastructure and core processing logic #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

skarjala
Copy link
Contributor

@skarjala skarjala commented Jul 1, 2025

Summary:

  • Implements functionality for processing multiple rank log files with new --all-ranks flag
  • Core Processing Logic: handle_all_ranks() function for multi rank processing

Future work:

  • landing page HTML generation and template system integration

@ezyang
Copy link
Contributor

ezyang commented Jul 2, 2025

If LLMs were used please post the prompts, thanks! You can use #114 as a model. (If not used that is fine too, I just want to check!)

@skarjala skarjala requested review from xmfan, bdhirsh and StrongerXi July 3, 2025 21:45
@skarjala skarjala marked this pull request as draft July 3, 2025 21:46
@skarjala skarjala marked this pull request as ready for review July 4, 2025 03:10
Copy link

@StrongerXi StrongerXi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped some nits on code dup, but I can see how the duplication might be intentional because all-rank processing might end up (or already is) diverging from one-rank processing, so you make the call:).

@@ -92,11 +100,12 @@ fn main() -> anyhow::Result<()> {
strict: cli.strict,
strict_compile_id: cli.strict_compile_id,
custom_parsers: Vec::new(),
custom_header_html: cli.custom_header_html,
custom_header_html: cli.custom_header_html.clone(),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why clone now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not mistaken, this is necessary because ParseConfig needs to own the string and cli is passed by reference.

Copy link
Member

@xmfan xmfan Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The emphasis of the question is on "why now" rather than just "why", the code compiled and seemed to work already before.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what happened is during your changes, cli was changed to a reference. It no longer is in this current PR version, so this .clone() shouldn't be necessary here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, in my current cli.rs file, I would need the clone in handle_one_rank() because handle_one_rank takes in &Cli and can't move the String out of a borrowed struct. Is this right?

Copy link
Member

@xmfan xmfan Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clone happens because you want to create ParseConfig using a Cli ref, but do you need 1 ParseConfig per rank?

Hint: Are there differences between the ParseConfig created for each rank?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm since ParseConfig is the same for all ranks, I could create ParseConfig once before processing ranks. And then pass that to handle_one_rank instead of passing cli. This would avoid repeated cloning of custom_header_html.

Is this the direction you're suggesting?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that sounds reasonable

src/cli.rs Outdated

// Extract rank number from filename
let rank_num = if let Some(pos) = rank_name.find("rank_") {
let after_rank = &rank_name[pos + 5..];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take a look at String.strip_prefix

src/cli.rs Outdated
let after_rank = &rank_name[pos + 5..];
after_rank
.chars()
.take_while(|c| c.is_ascii_digit())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are the log file name patterns you need to match?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it supports rank_N.log where N is a numeric variable. The logic uses strip_prefix("rank_") then extracts consecutive ASCII digits as the rank number. Ex:

  • rank_0.log, rank_1.log, rank_10.log
  • rank_0_worker.log
  • something_rank_5.log

But rejects files without the "rank_" prefix or without digits after it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, does the compiler logs actually output log file names with that variance? You only need to handle the filenames logged via TORCH_TRACE right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I'll take out the unnecessary complexity.

src/cli.rs Outdated
}

// Add link to this rank's page
rank_links.push((rank_num.clone(), format!("rank_{rank_num}/index.html")));
Copy link
Member

@xmfan xmfan Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't want to hard code this here. Suppose someone changes the single rank codepath to change the directory where index.html is stored, then this line would break! Is there a way we could ensure that this line remains relevant even if the single rank logic changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! Now it uses the actual output path returned by handle_one_rank() instead of hardcoding "index.html".

I tested this by running both single rank and multi-rank processing with different output directories and verified that the generated links correctly point to the actual output files in their respective subdirectories, so that it will remain correct even if the single-rank output structure changes.

@skarjala skarjala marked this pull request as draft July 7, 2025 20:37
@skarjala skarjala marked this pull request as ready for review July 7, 2025 20:51
@skarjala skarjala requested review from xmfan and StrongerXi July 7, 2025 21:02
src/cli.rs Outdated
}

fn main() -> anyhow::Result<()> {
let cli = Cli::parse();

if cli.all_ranks {
return handle_all_ranks(&cli);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cli is no longer needed after handle_all_ranks, which means you can take by value, which in rust defaults to move semantics unless the Cli type implements the Copy trait (it doesn't).

Copy link
Member

@xmfan xmfan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend splitting this PR up a bit. There's pre-existing code that can be refactored e.g. PathBuf logic, handle_one_rank helper. Then there can be another PR to add handle_all_ranks


// Add link to this rank's page using the actual output path from handle_one_rank
let rank_link = format!("rank_{}/{}", rank_num, main_output_path.display());
rank_links.push((rank_num.clone(), rank_link));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rank_links.push((rank_num.clone(), rank_link));
rank_links.push((rank_num, rank_link));

rank_path: &PathBuf,
rank_out_dir: &PathBuf,
cli: &Cli,
create_output_dir: bool,
Copy link
Member

@xmfan xmfan Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_output_dir seems avoidable

Try rewriting your callsites, so that this function either always creates the output directory, or the callsite always creates the output directory before calling into this.

Hint: can you rewrite all your newly added directory creating logic using setup_output_directory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, this would mean to remove the create_output_dir parameter and move directory creation to the callsites:

Single rank: uses existing setup_output_directory
Multi-rank: calls fs::create_dir(&rank_out_dir)? before handle_one_rank

Is this what you meant?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that, or you could always call setup_output_directory from within handle_one_rank

let path = if cli.latest {
let input_path = cli.path;
let input_path = &cli.path;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't look like this change is needed

handle_one_rank(&path, &out_path, &cli, false)?;

if !cli.no_browser {
opener::open(out_path.join("index.html"))?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's MAIN_OUTPUT_FILENAME for?

}

// Extract rank number from the pattern
let after_prefix = &filename[31..]; // Remove "dedicated_log_torch_trace_rank_"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strip_prefix?

Copy link
Member

@xmfan xmfan Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

look at line 225, looks very very similar. could we avoid computing things twice?

Maybe it's easier to express if you didn't use .filter here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid computing things twice, I'm intending to use filter_map to extract rank numbers once during collection, then returning Vec<(DirEntry, String)>. Is this approach feasible to address your feedback?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that can work. I was just thinking of falling back this logic to a foreach loop

};

// Only support PyTorch TORCH_TRACE files: dedicated_log_torch_trace_rank_0_hash.log
if !filename.starts_with("dedicated_log_torch_trace_rank_")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skarjala skarjala marked this pull request as draft July 14, 2025 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants