Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions docs/ops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# GGML Operations

List of GGML operations and backend support status.

Legend:
- ✅ Fully supported by this backend
- 🟡 Partially supported by this backend
- ❌ Not supported by this backend

| Operation | CPU | CUDA |
|-----------|------|------|
| ABS | ✅ | 🟡 |
| ACC | ✅ | ✅ |
| ADD | ✅ | ✅ |
| ADD1 | ✅ | ✅ |
| ARANGE | ✅ | ✅ |
| ARGMAX | ✅ | ✅ |
| ARGSORT | ✅ | ✅ |
| CLAMP | ✅ | ✅ |
| CONCAT | ✅ | 🟡 |
| CONT | ✅ | 🟡 |
| CONV_2D_DW | ✅ | ✅ |
| CONV_TRANSPOSE_1D | ✅ | ✅ |
| CONV_TRANSPOSE_2D | ✅ | ✅ |
| COS | ✅ | ✅ |
| COUNT_EQUAL | ✅ | ✅ |
| CPY | 🟡 | 🟡 |
| CROSS_ENTROPY_LOSS | ✅ | ✅ |
| CROSS_ENTROPY_LOSS_BACK | ✅ | ✅ |
| DIAG_MASK_INF | ✅ | ✅ |
| DIV | ✅ | ✅ |
| DUP | ✅ | 🟡 |
| ELU | ✅ | ❌ |
| EXP | ✅ | 🟡 |
| FLASH_ATTN_EXT | ✅ | 🟡 |
| GATED_LINEAR_ATTN | ✅ | ✅ |
| GEGLU | ✅ | ✅ |
| GELU | ✅ | 🟡 |
| GELU_ERF | ✅ | 🟡 |
| GELU_QUICK | ✅ | 🟡 |
| GET_ROWS | ✅ | 🟡 |
| GET_ROWS_BACK | 🟡 | 🟡 |
| GROUP_NORM | ✅ | ✅ |
| HARDSIGMOID | ✅ | 🟡 |
| HARDSWISH | ✅ | 🟡 |
| IM2COL | ✅ | ✅ |
| L2_NORM | ✅ | ✅ |
| LEAKY_RELU | ✅ | ✅ |
| LOG | ✅ | ✅ |
| MEAN | ✅ | ✅ |
| MUL | ✅ | ✅ |
| MUL_MAT | 🟡 | 🟡 |
| MUL_MAT_ID | ✅ | ✅ |
| NEG | ✅ | 🟡 |
| NORM | ✅ | ✅ |
| OPT_STEP_ADAMW | ✅ | ✅ |
| OUT_PROD | 🟡 | 🟡 |
| PAD | ✅ | ✅ |
| PAD_REFLECT_1D | ✅ | ❌ |
| POOL_2D | ✅ | ✅ |
| REGLU | ✅ | ✅ |
| RELU | ✅ | 🟡 |
| REPEAT | ✅ | 🟡 |
| REPEAT_BACK | ✅ | ✅ |
| RMS_NORM | ✅ | ✅ |
| RMS_NORM_BACK | ✅ | ✅ |
| RMS_NORM_MUL | ✅ | ✅ |
| ROLL | ✅ | ❌ |
| ROPE | ✅ | ✅ |
| ROPE_BACK | ✅ | ✅ |
| RWKV_WKV6 | ✅ | ✅ |
| RWKV_WKV7 | ✅ | ✅ |
| SCALE | ✅ | ✅ |
| SET | ✅ | ❌ |
| SET_ROWS | 🟡 | ❌ |
| SGN | ✅ | 🟡 |
| SIGMOID | ✅ | 🟡 |
| SILU | ✅ | 🟡 |
| SILU_BACK | ✅ | ✅ |
| SIN | ✅ | ✅ |
| SOFT_MAX | ✅ | ✅ |
| SOFT_MAX_BACK | 🟡 | 🟡 |
| SQR | ✅ | ✅ |
| SQRT | ✅ | ✅ |
| SSM_CONV | ✅ | ✅ |
| SSM_SCAN | ✅ | ✅ |
| STEP | ✅ | 🟡 |
| SUB | ✅ | ✅ |
| SUM | ✅ | ✅ |
| SUM_ROWS | ✅ | ✅ |
| SWIGLU | ✅ | ✅ |
| TANH | ✅ | 🟡 |
| TIMESTEP_EMBEDDING | ✅ | ✅ |
| UPSCALE | ✅ | 🟡 |
2 changes: 1 addition & 1 deletion include/ggml.h
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ extern "C" {
GGML_OP_POOL_1D,
GGML_OP_POOL_2D,
GGML_OP_POOL_2D_BACK,
GGML_OP_UPSCALE, // nearest interpolate
GGML_OP_UPSCALE,
GGML_OP_PAD,
GGML_OP_PAD_REFLECT_1D,
GGML_OP_ROLL,
Expand Down
192 changes: 192 additions & 0 deletions scripts/create_ops_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/usr/bin/env python3

"""
This script parses docs/ops/*.txt and creates the ops.md, which is a table documenting supported operations on various ggml backends.
"""
import os
import re
from pathlib import Path
from typing import Dict, List, Set
from collections import defaultdict


class DocsGenerator:
def __init__(self, ggml_root: str):
self.ggml_root = Path(ggml_root)
self.ops_dir = self.ggml_root / "docs" / "ops"
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()

def parse_support_files(self) -> None:
if not self.ops_dir.exists():
print(f"Warning: ops directory not found: {self.ops_dir}")
return

print(f"Parsing support files from {self.ops_dir}...")

for backend_dir in self.ops_dir.iterdir():
if not backend_dir.is_dir():
continue

backend_name = backend_dir.name
self.all_backends.add(backend_name)

print(f" Processing backend: {backend_name}")

for support_file in backend_dir.glob("*.txt"):
print(f" Reading: {support_file.name}")
self._parse_support_file(support_file, backend_name)

def _parse_support_file(self, file_path: Path, backend_name: str) -> None:
try:
with open(file_path, "r") as f:
content = f.read()

for line in content.split("\n"):
line = line.strip()

if line.startswith("supported,"):
parts = line.split(",")
if len(parts) >= 3:
operation = parts[1].strip()
supported_str = parts[2].strip()

if not operation or operation in [
"CONTEXT_ERROR",
"BUILD_ERROR",
]:
continue

is_supported = supported_str.lower() == "yes"

self.backend_support[backend_name][operation].append(
is_supported
)
self.all_operations.add(operation)

except Exception as e:
print(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("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:
print("Parsing GGML operation support files...")
self.parse_support_files()

if not self.all_operations:
print(
"No operations found. Make sure to run test-backend-ops support > docs/ops/BACKEND/file.txt first."
)
return

print(
f"Found {len(self.all_operations)} operations across {len(self.all_backends)} backends"
)

print("Generating markdown...")
markdown_content = self.generate_markdown()

docs_dir = self.ggml_root / "docs"
docs_dir.mkdir(exist_ok=True)

ops_file = docs_dir / "ops.md"
with open(ops_file, "w") as f:
f.write(markdown_content)

print(f"Generated: {ops_file}")
print(f"Operations: {len(self.all_operations)}")
print(f"Backends: {len(self.all_backends)}")


def main():
generator = DocsGenerator(".")
generator.run()


if __name__ == "__main__":
main()
49 changes: 49 additions & 0 deletions tests/test-backend-ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ enum test_mode {
MODE_TEST,
MODE_PERF,
MODE_GRAD,
MODE_SUPPORT,
};

struct test_case {
Expand Down Expand Up @@ -5028,6 +5029,51 @@ static bool test_backend(ggml_backend_t backend, test_mode mode, const char * op
return true;
}

if (mode == MODE_SUPPORT) {
auto test_cases = make_test_cases_eval();
filter_test_cases(test_cases, params_filter);
for (auto & test : test_cases ) {
ggml_init_params params = {
/*.mem_size =*/ 256*1024*1024,
/*.mem_buffer =*/ NULL,
/*.no_alloc =*/ true,
};
ggml_context * ctx = ggml_init(params);

if (ctx == NULL) {
printf("supported,CONTEXT_ERROR,no,error\n");
continue;
}

ggml_tensor * out = test->build_graph(ctx);

if (out == NULL) {
printf("supported,BUILD_ERROR,no,error\n");
ggml_free(ctx);
continue;
}

std::string op_desc = test->op_desc(out);

if (op_name != nullptr && op_desc.find(op_name) == std::string::npos) {
ggml_free(ctx);
continue;
}

bool supported = ggml_backend_supports_op(backend, out);

std::string test_vars = test->vars();

printf("supported,%s,%s,%s\n",
op_desc.c_str(),
supported ? "yes" : "no",
test_vars.c_str());

ggml_free(ctx);
}
return true;
}

GGML_ABORT("fatal error");
}

Expand All @@ -5037,6 +5083,7 @@ static void usage(char ** argv) {
printf(" - test (default, compare with CPU backend for correctness)\n");
printf(" - grad (compare gradients from backpropagation with method of finite differences)\n");
printf(" - perf (performance evaluation)\n");
printf(" - support (check if operations are supported for some backend)\n");
printf(" op names for -o are as given by ggml_op_desc() (e.g. ADD, MUL_MAT, etc)\n");
}

Expand All @@ -5053,6 +5100,8 @@ int main(int argc, char ** argv) {
mode = MODE_PERF;
} else if (strcmp(argv[i], "grad") == 0) {
mode = MODE_GRAD;
} else if (strcmp(argv[i], "support") == 0) {
mode = MODE_SUPPORT;
} else if (strcmp(argv[i], "-o") == 0) {
if (i + 1 < argc) {
op_name_filter = argv[++i];
Expand Down