Conversation
534af0b to
368adad
Compare
|
Some high-level thoughts first:
Should we somehow inform the users about it and take confirmation before proceeding?
Should this be Find the kernel at: https://hf.co/drbh/some-kernel/tree/{version}? This way, users can directly use that link to inspect files, etc. |
sayakpaul
left a comment
There was a problem hiding this comment.
Very minor comments. No RUST expertise yet.
| @@ -0,0 +1,38 @@ | |||
| --- | |||
There was a problem hiding this comment.
Do we want to maintain two copies? We have one in https://github.com/huggingface/kernels/blob/main/kernels/src/kernels/cli/card_template.md
|
|
||
| /// Nix flake target to run. | ||
| #[arg(long, default_value = "build-and-copy")] | ||
| pub target: BuildTarget, |
There was a problem hiding this comment.
I think top-level
kernel-builder build
kernel-builder build-and-copy
kernel-builder build-and-upload
are nicer, easier to discover. I think we can use #[command(flatten)] to share arguments between different build commands, something like:
// ...
Build {
#[command(flatten)]
pub common_build_args: CommonBuildArgs,
// ...
}
|
|
||
| /// Additional arguments passed through to `nix run`. | ||
| #[arg(last = true)] | ||
| pub nix_args: Vec<String>, |
| let bare_name = path.file_name().and_then(OsStr::to_str).ok_or_else(|| { | ||
| eyre::eyre!("Cannot determine directory name from `{path_str}`") | ||
| })?; | ||
| let owner = hf::whoami_username()?; |
| for (from, to) in replacements { | ||
| text = text.replace(from, to); | ||
| } | ||
| fs::write(&destination, text).wrap_err_with(|| { |
There was a problem hiding this comment.
I possible (not sure if it supports everything needed), I think it would be nice to use the fileset API that is also used for generating pyproject files. The nice thing is that it is atomic, if something fails while still preparing the output, nothing is written.
I think we would have to extend it with the notion of files that should not be overwritten (if a project already exists and you only want to add new files). But I think making it impossible to have an operation half-completed is a nice feature.
| .wrap_err_with(|| format!("Cannot parse TOML in `{}`", build_toml_path.display()))?; | ||
|
|
||
| // Update [general].backends | ||
| if let Some(general) = document.get_mut("general").and_then(|v| v.as_table_mut()) { |
There was a problem hiding this comment.
I forget if we discussed this before, but it would be worthwhile putting jinja templates in the template, so that we can render everything editing TOML (but maybe parse + write to format).
This has the benefit that if we ever change the build.toml format, we don't have to change it both in the template and here in the code.
E.g., suppose that backends moves somewhere else, we have to change it in the template and here.
| let mut impl_block = String::new(); | ||
| for (i, (condition, device, ref_prefix)) in conditions.iter().enumerate() { | ||
| let directive = if i == 0 { "#if" } else { "#elif" }; | ||
| impl_block.push_str(directive); | ||
| impl_block.push(' '); | ||
| impl_block.push_str(condition); | ||
| impl_block.push_str("\n ops.impl(\""); | ||
| impl_block.push_str(func_name); | ||
| impl_block.push_str("\", "); | ||
| impl_block.push_str(device); | ||
| impl_block.push_str(", "); | ||
| impl_block.push_str(ref_prefix); | ||
| impl_block.push_str(func_name); | ||
| impl_block.push_str(");\n"); | ||
| } |
There was a problem hiding this comment.
Could be a jinja template with if blocks.
| let marker = "ops.def(\""; | ||
| let start = content.find(marker)? + marker.len(); | ||
| let rest = &content[start..]; | ||
| let end = rest.find('(')?; | ||
| Some(&rest[..end]) | ||
| } | ||
|
|
||
| /// Replace `#if defined(CPU_KERNEL)...#endif` block with new content | ||
| fn replace_ifdef_block(content: &str, replacement: &str) -> String { | ||
| const START_MARKER: &str = "#if defined(CPU_KERNEL)"; | ||
| const END_MARKER: &str = "#endif"; | ||
|
|
||
| let Some(start) = content.find(START_MARKER) else { | ||
| return content.to_owned(); | ||
| }; | ||
|
|
||
| let search_region = &content[start..]; | ||
| let Some(end_offset) = search_region.find(END_MARKER) else { | ||
| return content.to_owned(); | ||
| }; | ||
|
|
||
| let end = start + end_offset + END_MARKER.len(); | ||
|
|
||
| let mut result = String::with_capacity(content.len()); | ||
| result.push_str(&content[..start]); | ||
| result.push_str(replacement); | ||
| result.push_str(&content[end..]); | ||
| result | ||
| } |
There was a problem hiding this comment.
Would be easier to maintain with a jinja template and some if blocks I think.
| /// Upload kernel build artifacts to the Hugging Face Hub. | ||
| Upload(UploadArgs), | ||
|
|
||
| #[command(hide = true)] |
There was a problem hiding this comment.
If I understand correctly, this removes the subcommand from help. Sounds bad? Leftover from testing?
This PR is a WIP to add the init command to the kernel-builder rust cli. This branch mainly ports the init command from the python cli, with some improvements to the interface and updates to the templateThis PR adds an the
initanduploadcommand to the rust kernel-builder cli similar to implementation in the current python cli. It also adds abuildcommand that is a facade for thenix runso devs can interact with a single tool for the full creation lifecycle of a kernel.Mainly it aims to have better defaults to make the commands easier to use
initcommand only requires the name of the kernels (folder) and the owner is pulled via awhoamirequest if the user is already logged into hfinitcommand creates a new dir if a name is specified, if called within a dir, the cli will use the dir name as the kernel nameuploadcommand does not require a repo id if the id can be read from the builduploadcommand does not require a path to the build folder and will default to looking for thebuilddir if no explicit path is specifiedexample usage
init from inside of a existing dir
init from outside of a dir
build
upload