llama-cpp-turboquant/ggml/src/ggml-virtgpu/regenerate_remoting.py
Kevin Pouget ffaafde16f
ggml-virtgpu: improve the reliability of the code (#19846)
* ggml-virtgpu-backend: validate the consistency of the received objects

This patch adds consistency checks in the
ggml-virtgpu-backend (running on the host side) to ensure that the
data received from the guest is consistent (valid pointers, valid
sizes and offsets).

* ggml-virtgpu-backend: add fallback/skips for optional ggml backend methods

```
  1. bck->iface.synchronize(bck)
  2. buft->iface.get_alloc_size(buft, op)
  3. buft->iface.get_max_size(buft)
```

these three methods are optional in the GGML interface. `get_max_size`
was already properly defaulted, but `backend sychronize` and `butf
get_max_size` would have segfaulted the backend if not implemented.

* ggml-virtgpu-backend: fix log format missing argument

* ggml-virtgpu-backend: improve the abort message

* ggml-virtgpu-backend: more safety checks

* ggml-virtgpu-backend: new error code

* ggml-virtgpu-backend: initialize all the error codes

* ggml-virtgpu: add a missing comment generated by the code generator

* ggml-virtgpu: add the '[virtgpu]' prefix to the device/buffer names

* ggml-virtgpu: apir_device_buffer_from_ptr: improve the error message

* ggml-virtgpu: shared: make it match the latest api_remoting.h of Virglrenderer APIR

(still unmerged)

* ggml-virtgpu: update the code generator to have dispatch_command_name in a host/guest shared file

* ggml-virtgpu: REMOTE_CALL: fail if the backend returns an error

* docs/backend/VirtGPU.md: indicate that the RAM+VRAM size is limed to 64 GB with libkrun

* ggml-virtgpu: turn off clang-format header ordering for some of the files

Compilation breaks when ordered alphabetically.

* ggml-virtgpu: clang-format

* ggml-virtgpu/backend/shared/api_remoting: better comments for the APIR return codes
2026-02-26 20:00:57 +08:00

333 lines
13 KiB
Python
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
# Generated by Claude AI
Script to completely regenerate the GGML remoting codebase from YAML configuration.
This script reads api_functions.yaml and regenerates all the header files and
implementation templates for the GGML remoting layer.
Usage:
python regenerate_remoting.py
The script will:
1. Read ggmlremoting_functions.yaml configuration
2. Generate updated header files
3. Generate implementation templates in dedicated files
4. Show a summary of what was generated
"""
import yaml
from typing import Dict, List, Any
from pathlib import Path
import os
import subprocess
import shutil
import logging
NL = '\n' # can't have f"{'\n'}" in f-strings
class RemotingCodebaseGenerator:
def __init__(self, yaml_path: str = "ggmlremoting_functions.yaml"):
"""Initialize the generator with the YAML configuration."""
self.yaml_path = yaml_path
if not Path(yaml_path).exists():
raise FileNotFoundError(f"Configuration file {yaml_path} not found")
with open(yaml_path, 'r') as f:
self.config = yaml.safe_load(f)
self.functions = self.config['functions']
self.naming_patterns = self.config['naming_patterns']
self.config_data = self.config['config']
# Check if clang-format is available
self.clang_format_available = self._check_clang_format_available()
def _check_clang_format_available(self) -> bool:
"""Check if clang-format is available in the system PATH."""
return shutil.which("clang-format") is not None
def _format_file_with_clang_format(self, file_path: Path) -> bool:
"""Format a file with clang-format -i. Returns True if successful, False otherwise."""
if not self.clang_format_available:
return False
try:
subprocess.run(
["clang-format", "-i", str(file_path)],
check=True,
capture_output=True,
text=True
)
return True
except subprocess.CalledProcessError:
logging.exception(f" ⚠️ clang-format failed for {file_path}")
return False
except Exception as e:
logging.exception(f" ⚠️ Unexpected error formatting {file_path}: {e}")
return False
def generate_enum_name(self, group_name: str, function_name: str) -> str:
"""Generate the APIR_COMMAND_TYPE enum name for a function."""
prefix = self.naming_patterns['enum_prefix']
return f"{prefix}{group_name.upper()}_{function_name.upper()}"
def generate_backend_function_name(self, group_name: str, function_name: str) -> str:
"""Generate the backend function name."""
function_key = f"{group_name}_{function_name}"
overrides = self.naming_patterns.get('backend_function_overrides', {})
if function_key in overrides:
return overrides[function_key]
prefix = self.naming_patterns['backend_function_prefix']
return f"{prefix}{group_name}_{function_name}"
def generate_frontend_function_name(self, group_name: str, function_name: str) -> str:
"""Generate the frontend function name."""
prefix = self.naming_patterns['frontend_function_prefix']
return f"{prefix}{group_name}_{function_name}"
def get_enabled_functions(self) -> List[Dict[str, Any]]:
"""Get all enabled functions with their metadata."""
functions = []
enum_value = 0
for group_name, group_data in self.functions.items():
group_description = group_data['group_description']
for function_name, func_metadata in group_data['functions'].items():
# Handle case where func_metadata is None or empty (functions with only comments)
if func_metadata is None:
func_metadata = {}
# Functions are enabled by default unless explicitly disabled
if func_metadata.get('enabled', True):
functions.append({
'group_name': group_name,
'function_name': function_name,
'enum_name': self.generate_enum_name(group_name, function_name),
'enum_value': enum_value,
'backend_function': self.generate_backend_function_name(group_name, function_name),
'frontend_function': self.generate_frontend_function_name(group_name, function_name),
'frontend_return': func_metadata.get('frontend_return', 'void'),
'frontend_extra_params': func_metadata.get('frontend_extra_params', []),
'group_description': group_description,
'deprecated': func_metadata.get('deprecated', False),
})
enum_value += 1
return functions
def generate_apir_backend_header(self) -> str:
"""Generate the complete apir_backend.h file."""
functions = self.get_enabled_functions()
# Generate the enum section
enum_lines = ["typedef enum ApirBackendCommandType {"]
current_group = None
for func in functions:
# Add comment for new group
if func['group_name'] != current_group:
enum_lines.append("")
enum_lines.append(f" /* {func['group_description']} */")
current_group = func['group_name']
enum_lines.append(f" {func['enum_name']} = {func['enum_value']},")
# Add the count
total_count = len(functions)
enum_lines.append("\n // last command_type index + 1")
enum_lines.append(f" APIR_BACKEND_DISPATCH_TABLE_COUNT = {total_count},")
enum_lines.append("} ApirBackendCommandType;")
# Generate function name mapping
func_lines = []
func_lines.append("static inline const char * apir_dispatch_command_name(ApirBackendCommandType type) {")
func_lines.append(" switch (type) {")
current_group = None
for func in functions:
# Add comment for new group
if func['group_name'] != current_group:
func_lines.append(f" /* {func['group_description']} */")
current_group = func['group_name']
# Generate clean function name without backend_ prefix
clean_name = f"{func['group_name']}_{func['function_name']}"
func_lines.append(f" case {func['enum_name']}:")
func_lines.append(f" return \"{clean_name}\";")
func_lines.append("")
func_lines.append(" default:")
func_lines.append(" return \"unknown\";")
func_lines.append(" }")
func_lines.append("}")
# Full header template
header_content = NL.join(enum_lines) + "\n\n" + NL.join(func_lines) + "\n"
return header_content
def generate_backend_dispatched_header(self) -> str:
"""Generate the complete backend-dispatched.h file."""
functions = self.get_enabled_functions()
# Function declarations
decl_lines = []
current_group = None
for func in functions:
if func['group_name'] != current_group:
decl_lines.append(f"\n/* {func['group_description']} */")
current_group = func['group_name']
signature = "uint32_t"
params = "apir_encoder *enc, apir_decoder *dec, virgl_apir_context *ctx"
if func['deprecated']:
decl_lines.append(f"/* {func['enum_name']} is deprecated. Keeping the handler for backward compatibility. */")
decl_lines.append(f"{signature} {func['backend_function']}({params});")
# Dispatch table
table_lines = []
current_group = None
for func in functions:
if func['group_name'] != current_group:
table_lines.append(f"\n /* {func['group_description']} */")
table_lines.append("")
current_group = func['group_name']
deprecated = " /* DEPRECATED */" if func['deprecated'] else ""
table_lines.append(f" /* {func['enum_name']} = */ {func['backend_function']}{deprecated},")
header_content = f'''\
#pragma once
{NL.join(decl_lines)}
extern "C" {{
static const backend_dispatch_t apir_backend_dispatch_table[APIR_BACKEND_DISPATCH_TABLE_COUNT] = {{
{NL.join(table_lines)}
}};
}}
'''
return header_content
def generate_virtgpu_forward_header(self) -> str:
"""Generate the complete virtgpu-forward.gen.h file."""
functions = self.get_enabled_functions()
decl_lines = []
current_group = None
for func in functions:
if func['group_name'] != current_group:
decl_lines.append("")
decl_lines.append(f"/* {func['group_description']} */")
current_group = func['group_name']
if func['deprecated']:
decl_lines.append(f"/* {func['frontend_function']} is deprecated. */")
continue
# Build parameter list
params = [self.naming_patterns['frontend_base_param']]
params.extend(func['frontend_extra_params'])
param_str = ', '.join(params)
decl_lines.append(f"{func['frontend_return']} {func['frontend_function']}({param_str});")
header_content = f'''\
#pragma once
{NL.join(decl_lines)}
'''
return header_content
def regenerate_codebase(self) -> None:
"""Regenerate the entire remoting codebase."""
logging.info("🔄 Regenerating GGML Remoting Codebase...")
logging.info("=" * 50)
# Detect if we're running from frontend directory
current_dir = os.getcwd()
is_frontend_dir = current_dir.endswith('ggml-virtgpu')
if is_frontend_dir:
# Running from ggml/src/ggml-virtgpu-apir
logging.info("📍 Detected frontend directory execution")
frontend_base = Path(".")
else:
# Running from project root (fallback to original behavior)
logging.info("📍 Detected project root execution")
base_path = self.config_data.get('base_path', 'ggml/src')
frontend_base = Path(base_path) / "ggml-virtgpu"
# Compute final file paths
backend_base = frontend_base / "backend"
apir_backend_path = backend_base / "shared" / "apir_backend.gen.h"
backend_dispatched_path = backend_base / "backend-dispatched.gen.h"
virtgpu_forward_path = frontend_base / "virtgpu-forward.gen.h"
# Create output directories for each file
apir_backend_path.parent.mkdir(parents=True, exist_ok=True)
backend_dispatched_path.parent.mkdir(parents=True, exist_ok=True)
virtgpu_forward_path.parent.mkdir(parents=True, exist_ok=True)
# Generate header files
logging.info("📁 Generating header files...")
apir_backend_content = self.generate_apir_backend_header()
apir_backend_path.write_text(apir_backend_content)
logging.info(f"{apir_backend_path.resolve()}")
backend_dispatched_content = self.generate_backend_dispatched_header()
backend_dispatched_path.write_text(backend_dispatched_content)
logging.info(f"{backend_dispatched_path.resolve()}")
virtgpu_forward_content = self.generate_virtgpu_forward_header()
virtgpu_forward_path.write_text(virtgpu_forward_content)
logging.info(f"{virtgpu_forward_path.resolve()}")
# Format generated files with clang-format
generated_files = [apir_backend_path, backend_dispatched_path, virtgpu_forward_path]
if not self.clang_format_available:
logging.warning("\nclang-format not found in PATH. Generated files will not be formatted.\n"
" Install clang-format to enable automatic code formatting.")
else:
logging.info("\n🎨 Formatting files with clang-format...")
for file_path in generated_files:
if self._format_file_with_clang_format(file_path):
logging.info(f" ✅ Formatted {file_path.name}")
else:
logging.warning(f" ❌ Failed to format {file_path.name}")
# Generate summary
functions = self.get_enabled_functions()
total_functions = len(functions)
logging.info("\n📊 Generation Summary:")
logging.info("=" * 50)
logging.info(f" Total functions: {total_functions}")
logging.info(f" Function groups: {len(self.functions)}")
logging.info(" Header files: 3")
logging.info(f" Working directory: {current_dir}")
def main():
try:
generator = RemotingCodebaseGenerator()
generator.regenerate_codebase()
except Exception as e:
logging.exception(f"❌ Error: {e}")
exit(1)
if __name__ == "__main__":
main()