From b333b06772c89d96aacb5490d6a219fba7c09cc6 Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Thu, 12 Feb 2026 20:57:17 +0100 Subject: Engage! --- llama.cpp/scripts/create_ops_docs.py | 201 +++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100755 llama.cpp/scripts/create_ops_docs.py (limited to 'llama.cpp/scripts/create_ops_docs.py') diff --git a/llama.cpp/scripts/create_ops_docs.py b/llama.cpp/scripts/create_ops_docs.py new file mode 100755 index 0000000..e3a476a --- /dev/null +++ b/llama.cpp/scripts/create_ops_docs.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 + +""" +This script parses docs/ops/*.csv and creates the ops.md, which is a table documenting supported operations on various ggml backends. +""" +import csv +import logging +import sys +from pathlib import Path +from collections import defaultdict + + +class DocsGenerator: + def __init__(self, ggml_root: str, output_filename: str = "ops.md"): + self.ggml_root = Path(ggml_root) + self.ops_dir = self.ggml_root / "docs" / "ops" + self.output_filename = output_filename + self.backend_support: dict[str, dict[str, list[bool]]] = defaultdict( + lambda: defaultdict(list) + ) + self.all_operations: set[str] = set() + self.all_backends: set[str] = set() + self.logger = logging.getLogger(__name__) + + def parse_support_files(self) -> None: + if not self.ops_dir.exists(): + self.logger.warning(f"ops directory not found: {self.ops_dir}") + return + + self.logger.info(f"Parsing support files from {self.ops_dir}...") + + for support_file in self.ops_dir.glob("*.csv"): + self.logger.info(f" Reading: {support_file.name}") + self._parse_support_file(support_file) + + def _parse_support_file(self, file_path: Path) -> None: + try: + with open(file_path, "r", newline='') as f: + reader = csv.DictReader(f) + + for row in reader: + # Skip rows that don't have support mode + if row.get('test_mode') != 'support': + continue + + backend_name = row.get('backend_name', '').strip() + operation = row.get('op_name', '').strip() + supported_str = row.get('error_message', '').strip() # "yes" or "no" + backend_reg_name = row.get('backend_reg_name', '').strip() + + # Skip invalid or error operations + if not operation or not backend_name or operation in [ + "CONTEXT_ERROR", + "BUILD_ERROR", + ]: + continue + + is_supported = supported_str.lower() == "yes" + + # Use backend_reg_name for grouping, fallback to backend_name + backend_key = backend_reg_name if backend_reg_name else backend_name + + self.all_backends.add(backend_key) + self.backend_support[backend_key][operation].append(is_supported) + self.all_operations.add(operation) + + except Exception as e: + self.logger.error(f" Error parsing {file_path}: {e}") + + def get_backend_support_status(self, backend: str, operation: str) -> str: + support_list = self.backend_support[backend].get(operation, []) + + if not support_list: + return "unsupported" + + all_supported = all(support_list) + any_supported = any(support_list) + + if all_supported: + return "supported" + elif any_supported: + return "partially supported" + else: + return "unsupported" + + def get_support_status(self, operation: str) -> str: + if operation not in self.all_operations: + return "unsupported" + + support_count = 0 + total_backends = len(self.all_backends) + + for backend in self.all_backends: + if self.backend_support[backend].get(operation, False): + support_count += 1 + + if support_count == 0: + return "unsupported" + elif support_count == total_backends: + return "supported" + else: + return "partially supported" + + def get_support_symbol(self, status: str) -> str: + symbols = {"supported": "✅", "partially supported": "🟡", "unsupported": "❌"} + return symbols.get(status, "❓") + + def generate_markdown(self) -> str: + lines = [] + + lines.append("# GGML Operations") + lines.append("") + lines.append("List of GGML operations and backend support status.") + lines.append("") + lines.append("## How to add a backend to this table:") + lines.append("") + lines.append("1. Run `test-backend-ops support --output csv` with your backend name and redirect output to a csv file in `docs/ops/` (e.g., `docs/ops/CUDA.csv`)") + lines.append("2. Regenerate `/docs/ops.md` via `./scripts/create_ops_docs.py`") + lines.append("") + lines.append("Legend:") + lines.append("- ✅ Fully supported by this backend") + lines.append("- 🟡 Partially supported by this backend") + lines.append("- ❌ Not supported by this backend") + lines.append("") + + backends = sorted(self.all_backends) + header = "| Operation |" + for backend in backends: + header += f" {backend} |" + + separator = "|-----------|" + for _ in backends: + separator += "------|" + + lines.append(header) + lines.append(separator) + + sorted_operations = sorted(self.all_operations) + + for operation in sorted_operations: + row = f"| {operation:>32} |" + + for backend in backends: + status = self.get_backend_support_status(backend, operation) + if status == "supported": + symbol = "✅" + elif status == "partially supported": + symbol = "🟡" + else: + symbol = "❌" + row += f" {symbol} |" + + lines.append(row) + + lines.append("") + + return "\n".join(lines) + + def run(self) -> None: + self.logger.info("Parsing GGML operation support files...") + self.parse_support_files() + + if not self.all_operations: + self.logger.error( + "No operations found. Make sure to run test-backend-ops support --output csv > docs/ops/file.csv first." + ) + return + + self.logger.info( + f"Found {len(self.all_operations)} operations across {len(self.all_backends)} backends" + ) + + self.logger.info("Generating markdown...") + markdown_content = self.generate_markdown() + + docs_dir = self.ggml_root / "docs" + docs_dir.mkdir(exist_ok=True) + + ops_file = docs_dir / self.output_filename + with open(ops_file, "w") as f: + f.write(markdown_content) + + self.logger.info(f"Generated: {ops_file}") + self.logger.info(f"Operations: {len(self.all_operations)}") + self.logger.info(f"Backends: {len(self.all_backends)}") + + +def main(): + logging.basicConfig(level=logging.INFO) + + if len(sys.argv) > 1: + output_filename = sys.argv[1] + else: + output_filename = "ops.md" + + generator = DocsGenerator(".", output_filename) + generator.run() + + +if __name__ == "__main__": + main() -- cgit v1.2.3