2025-07-10 23:29:01 +08:00
#!/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 ( " " )
2025-07-27 09:36:43 +08:00
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 ( " " )
2025-07-10 23:29:01 +08:00
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 ( )