-
-
Notifications
You must be signed in to change notification settings - Fork 11k
[Doc] Group examples into categories #11782
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
Merged
Merged
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
50f9d3a
Ignore all files in `docs/source/getting_started/examples`
hmellor c163f89
Fix skipped heading level in prometheus/grafana tutorial
hmellor 5936229
Fix code block hinting in FP8 tutorial
hmellor ff4504b
Add colon_fence and sphinx-togglebutton
hmellor 008e69a
Update `generate_examples.py`
hmellor a72bff3
Add missing OpenAI batch example
hmellor 929adac
Make typing Python 3.8 compatible for RTD
hmellor 5c57a53
`Example source` -> `Example materials`
hmellor f0025d8
Make linter happy
hmellor ea22d7a
Delete md template
hmellor 6538bd8
Fix error with `chart-helm`
hmellor b01b7c8
Revert "Make typing Python 3.8 compatible for RTD"
hmellor 328f3e6
Make Python 3.12 linter happy
hmellor 71ce031
Split mixed example
hmellor 92d6abf
Categorise based on directory
hmellor 3853007
Make linter happy
hmellor 6c852a8
syntax error
hmellor 141bfbc
Put categorised examples first
hmellor 31972ea
Make `make clean` clear the generated examples
hmellor 960a017
fix case where SOURCEDIR contains whitespace
hmellor 12482c1
Make sure index pages are sorted alphabetically
hmellor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,54 +1,234 @@ | ||
| import itertools | ||
| import re | ||
| from dataclasses import dataclass, field | ||
| from pathlib import Path | ||
|
|
||
| ROOT_DIR = Path(__file__).parent.parent.parent.resolve() | ||
| ROOT_DIR_RELATIVE = '../../../..' | ||
| EXAMPLE_DIR = ROOT_DIR / "examples" | ||
| EXAMPLE_DOC_DIR = ROOT_DIR / "docs/source/getting_started/examples" | ||
|
|
||
|
|
||
| def fix_case(text: str) -> str: | ||
| subs = [ | ||
| ("api", "API"), | ||
| ("llm", "LLM"), | ||
| ("vllm", "vLLM"), | ||
| ("openai", "OpenAI"), | ||
| ("multilora", "MultiLoRA"), | ||
| ] | ||
| for sub in subs: | ||
| text = re.sub(*sub, text, flags=re.IGNORECASE) | ||
| subs = { | ||
| "api": "API", | ||
| "cpu": "CPU", | ||
| "llm": "LLM", | ||
| "tpu": "TPU", | ||
| "aqlm": "AQLM", | ||
| "gguf": "GGUF", | ||
| "lora": "LoRA", | ||
| "vllm": "vLLM", | ||
| "openai": "OpenAI", | ||
| "multilora": "MultiLoRA", | ||
| "mlpspeculator": "MLPSpeculator", | ||
| r"fp\d+": lambda x: x.group(0).upper(), # e.g. fp16, fp32 | ||
| r"int\d+": lambda x: x.group(0).upper(), # e.g. int8, int16 | ||
| } | ||
| for pattern, repl in subs.items(): | ||
| text = re.sub(rf'\b{pattern}\b', repl, text, flags=re.IGNORECASE) | ||
| return text | ||
|
|
||
|
|
||
| def generate_title(filename: str) -> str: | ||
| # Turn filename into a title | ||
| title = filename.replace("_", " ").title() | ||
| # Handle acronyms and names | ||
| title = fix_case(title) | ||
| return f"# {title}" | ||
| @dataclass | ||
| class Index: | ||
| """ | ||
| Index class to generate a structured document index. | ||
|
|
||
| Attributes: | ||
| path (Path): The path save the index file to. | ||
| title (str): The title of the index. | ||
| description (str): A brief description of the index. | ||
| caption (str): An optional caption for the table of contents. | ||
| maxdepth (int): The maximum depth of the table of contents. Defaults to 1. | ||
| documents (list[str]): A list of document paths to include in the index. Defaults to an empty list. | ||
|
|
||
| Methods: | ||
| generate() -> str: | ||
| Generates the index content as a string in the specified format. | ||
| """ # noqa: E501 | ||
| path: Path | ||
| title: str | ||
| description: str | ||
| caption: str | ||
| maxdepth: int = 1 | ||
| documents: list[str] = field(default_factory=list) | ||
|
|
||
| def generate(self) -> str: | ||
| content = f"# {self.title}\n\n{self.description}\n\n" | ||
| content += "```{toctree}\n" | ||
| content += f":caption: {self.caption}\n:maxdepth: {self.maxdepth}\n" | ||
| content += "\n".join(self.documents) + "\n```\n" | ||
| return content | ||
|
|
||
|
|
||
| @dataclass | ||
| class Example: | ||
| """ | ||
| Example class for generating documentation content from a given path. | ||
|
|
||
| Attributes: | ||
| path (Path): The path to the main directory or file. | ||
| category (str): The category of the document. | ||
| main_file (Path): The main file in the directory. | ||
| other_files (list[Path]): List of other files in the directory. | ||
| title (str): The title of the document. | ||
|
|
||
| Methods: | ||
| __post_init__(): Initializes the main_file, other_files, and title attributes. | ||
| determine_main_file() -> Path: Determines the main file in the given path. | ||
| determine_other_files() -> list[Path]: Determines other files in the directory excluding the main file. | ||
| determine_title() -> str: Determines the title of the document. | ||
| generate() -> str: Generates the documentation content. | ||
| """ # noqa: E501 | ||
| path: Path | ||
| category: str = None | ||
| main_file: Path = field(init=False) | ||
| other_files: list[Path] = field(init=False) | ||
| title: str = field(init=False) | ||
|
|
||
| def __post_init__(self): | ||
| self.main_file = self.determine_main_file() | ||
| self.other_files = self.determine_other_files() | ||
| self.title = self.determine_title() | ||
|
|
||
| def determine_main_file(self) -> Path: | ||
| """ | ||
| Determines the main file in the given path. | ||
| If the path is a file, it returns the path itself. Otherwise, it searches | ||
| for Markdown files (*.md) in the directory and returns the first one found. | ||
| Returns: | ||
| Path: The main file path, either the original path if it's a file or the first | ||
| Markdown file found in the directory. | ||
| Raises: | ||
| IndexError: If no Markdown files are found in the directory. | ||
| """ # noqa: E501 | ||
| return self.path if self.path.is_file() else list( | ||
| self.path.glob("*.md")).pop() | ||
|
|
||
| def determine_other_files(self) -> list[Path]: | ||
| """ | ||
| Determine other files in the directory excluding the main file. | ||
|
|
||
| This method checks if the given path is a file. If it is, it returns an empty list. | ||
| Otherwise, it recursively searches through the directory and returns a list of all | ||
| files that are not the main file. | ||
|
|
||
| Returns: | ||
| list[Path]: A list of Path objects representing the other files in the directory. | ||
| """ # noqa: E501 | ||
| if self.path.is_file(): | ||
| return [] | ||
| is_other_file = lambda file: file.is_file() and file != self.main_file | ||
| return [file for file in self.path.rglob("*") if is_other_file(file)] | ||
|
|
||
| def determine_title(self) -> str: | ||
| return fix_case(self.path.stem.replace("_", " ").title()) | ||
|
|
||
| def generate(self) -> str: | ||
| # Convert the path to a relative path from __file__ | ||
| make_relative = lambda path: ROOT_DIR_RELATIVE / path.relative_to( | ||
| ROOT_DIR) | ||
|
|
||
| content = f"Source <gh-file:{self.path.relative_to(ROOT_DIR)}>.\n\n" | ||
| if self.main_file.suffix == ".py": | ||
| content += f"# {self.title}\n\n" | ||
| include = "include" if self.main_file.suffix == ".md" else \ | ||
| "literalinclude" | ||
| content += f":::{{{include}}} {make_relative(self.main_file)}\n:::\n\n" | ||
|
|
||
| if not self.other_files: | ||
| return content | ||
|
|
||
| content += "## Example materials\n\n" | ||
| for file in self.other_files: | ||
| include = "include" if file.suffix == ".md" else "literalinclude" | ||
| content += f":::{{admonition}} {file.relative_to(self.path)}\n" | ||
| content += ":class: dropdown\n\n" | ||
| content += f":::{{{include}}} {make_relative(file)}\n:::\n" | ||
| content += ":::\n\n" | ||
|
|
||
| return content | ||
|
|
||
|
|
||
| def generate_examples(): | ||
| root_dir = Path(__file__).parent.parent.parent.resolve() | ||
|
|
||
| # Source paths | ||
| script_dir = root_dir / "examples" | ||
| script_paths = sorted(script_dir.glob("*.py")) | ||
|
|
||
| # Destination paths | ||
| doc_dir = root_dir / "docs/source/getting_started/examples" | ||
| doc_paths = [doc_dir / f"{path.stem}.md" for path in script_paths] | ||
|
|
||
| # Generate the example docs for each example script | ||
| for script_path, doc_path in zip(script_paths, doc_paths): | ||
| # Make script_path relative to doc_path and call it include_path | ||
| include_path = '../../../..' / script_path.relative_to(root_dir) | ||
| content = (f"{generate_title(doc_path.stem)}\n\n" | ||
| f"Source: <gh-file:examples/{script_path.name}>.\n\n" | ||
| f"```{{literalinclude}} {include_path}\n" | ||
| ":language: python\n" | ||
| ":linenos:\n```") | ||
| # Create the EXAMPLE_DOC_DIR if it doesn't exist | ||
| if not EXAMPLE_DOC_DIR.exists(): | ||
| EXAMPLE_DOC_DIR.mkdir(parents=True) | ||
|
|
||
| # Create empty indices | ||
| examples_index = Index( | ||
| path=EXAMPLE_DOC_DIR / "examples_index.md", | ||
| title="Examples", | ||
| description= | ||
| "A collection of examples demonstrating usage of vLLM.\nAll documented examples are autogenerated using <gh-file:docs/source/generate_examples.py> from examples found in <gh-file:examples>.", # noqa: E501 | ||
| caption="Examples", | ||
| maxdepth=1) # TODO change to 2 when examples start being categorised | ||
| category_indices = { | ||
| "offline_inference": | ||
| Index( | ||
| path=EXAMPLE_DOC_DIR / "examples_offline_inference_index.md", | ||
| title="Offline Inference", | ||
| description= | ||
| "Offline inference examples demonstrate how to use vLLM in an offline setting, where the model is queried for predictions in batches.", # noqa: E501 | ||
| caption="Examples", | ||
| ), | ||
| "online_serving": | ||
| Index( | ||
| path=EXAMPLE_DOC_DIR / "examples_online_serving_index.md", | ||
| title="Online Serving", | ||
| description= | ||
| "Online serving examples demonstrate how to use vLLM in an online setting, where the model is queried for predictions in real-time.", # noqa: E501 | ||
| caption="Examples", | ||
| ), | ||
| "other": | ||
| Index( | ||
| path=EXAMPLE_DOC_DIR / "examples_other_index.md", | ||
| title="Other", | ||
| description= | ||
| "Other examples that don't strongly fit into the online or offline serving categories.", # noqa: E501 | ||
| caption="Examples", | ||
| ), | ||
| } | ||
|
|
||
| examples = [] | ||
| # Find categorised examples | ||
| for category in category_indices: | ||
| category_dir = EXAMPLE_DIR / category | ||
| py = category_dir.glob("*.py") | ||
| md = category_dir.glob("*.md") | ||
| for path in itertools.chain(py, md): | ||
| examples.append(Example(path, category)) | ||
| # Find examples in subdirectories | ||
| for path in category_dir.glob("*/*.md"): | ||
| examples.append(Example(path.parent, category)) | ||
| # Find uncategorised examples | ||
| py = EXAMPLE_DIR.glob("*.py") | ||
| md = EXAMPLE_DIR.glob("*.md") | ||
| for path in itertools.chain(py, md): | ||
| examples.append(Example(path)) | ||
| # Find examples in subdirectories | ||
| for path in EXAMPLE_DIR.glob("*/*.md"): | ||
| # Skip categorised examples | ||
| if path.parent.name in category_indices: | ||
| continue | ||
| examples.append(Example(path.parent)) | ||
|
|
||
| # Generate the example documentation | ||
| for example in examples: | ||
| doc_path = EXAMPLE_DOC_DIR / f"{example.path.stem}.md" | ||
| with open(doc_path, "w+") as f: | ||
| f.write(content) | ||
|
|
||
| # Generate the toctree for the example scripts | ||
| with open(doc_dir / "examples_index.template.md") as f: | ||
| examples_index = f.read() | ||
| with open(doc_dir / "examples_index.md", "w+") as f: | ||
| example_docs = "\n".join(path.stem + ".md" for path in script_paths) | ||
| f.write(examples_index.replace(r"%EXAMPLE_DOCS%", example_docs)) | ||
| f.write(example.generate()) | ||
| # Add the example to the appropriate index | ||
| index = category_indices.get(example.category, examples_index) | ||
| index.documents.append(example.path.stem) | ||
|
|
||
| # Generate the index files | ||
| for category_index in category_indices.values(): | ||
| if category_index.documents: | ||
| examples_index.documents.insert(0, category_index.path.name) | ||
| with open(category_index.path, "w+") as f: | ||
| f.write(category_index.generate()) | ||
|
|
||
| with open(examples_index.path, "w+") as f: | ||
| f.write(examples_index.generate()) |
8 changes: 0 additions & 8 deletions
8
docs/source/getting_started/examples/examples_index.template.md
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.