From 8a2e4439ba196620f1b1a6e9868d0d8980352ec5 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 10 Jul 2026 14:56:41 +0800 Subject: [PATCH] Local project checks and contribution templates: make check, dependency-free C/python tests, issue forms + blank issues, CONTRIBUTING (#20) * Add lightweight project checks and templates * Ignore generated test binaries * Keep project checks local --- .github/ISSUE_TEMPLATE/bug_report.yml | 43 ++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 26 +++++++++++ .github/ISSUE_TEMPLATE/performance_report.yml | 45 +++++++++++++++++++ .github/pull_request_template.md | 14 ++++++ .gitignore | 2 + CONTRIBUTING.md | 23 ++++++++++ c/Makefile | 27 ++++++++++- c/benchmark_cuda_fixture.py | 15 ++++--- c/tests/test_benchmark_cuda_fixture.py | 24 ++++++++++ c/tests/test_json.c | 36 +++++++++++++++ c/tests/test_st.c | 26 +++++++++++ 12 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/performance_report.yml create mode 100644 .github/pull_request_template.md create mode 100644 CONTRIBUTING.md create mode 100644 c/tests/test_benchmark_cuda_fixture.py create mode 100644 c/tests/test_json.c create mode 100644 c/tests/test_st.c diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..4c961e5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,43 @@ +name: Bug report +description: Report a reproducible defect in Colibri. +title: "[Bug]: " +body: + - type: markdown + attributes: + value: Thanks for taking the time to provide a reproducible report. + - type: input + id: version + attributes: + label: Commit or version + description: Paste the exact commit hash when possible. + placeholder: git rev-parse HEAD + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: Include OS, CPU, RAM, GPU/CUDA if enabled, compiler, and storage type. + placeholder: Ubuntu 24.04, Ryzen 7950X, 64 GB RAM, NVMe, gcc 13 + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Reproduction steps + description: Include the exact build and run commands with sensitive paths removed. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual behavior and logs + render: shell + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0086358 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..7712446 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,26 @@ +name: Feature request +description: Propose a focused improvement to Colibri. +title: "[Feature]: " +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What real limitation or workflow problem does this address? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + - type: textarea + id: scope + attributes: + label: Scope and compatibility + description: Note effects on the zero-dependency CPU path, CUDA path, model formats, or platforms. diff --git a/.github/ISSUE_TEMPLATE/performance_report.yml b/.github/ISSUE_TEMPLATE/performance_report.yml new file mode 100644 index 0000000..b0ac642 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/performance_report.yml @@ -0,0 +1,45 @@ +name: Performance report +description: Report a benchmark result or performance regression. +title: "[Performance]: " +body: + - type: input + id: commit + attributes: + label: Commit + placeholder: git rev-parse HEAD + validations: + required: true + - type: textarea + id: hardware + attributes: + label: Hardware and storage + description: Include CPU, RAM, GPU/VRAM, disk model/type, and filesystem. + validations: + required: true + - type: textarea + id: software + attributes: + label: Software environment + description: Include OS, compiler, CUDA/driver versions, and relevant environment variables. + validations: + required: true + - type: textarea + id: command + attributes: + label: Build and benchmark commands + render: shell + validations: + required: true + - type: textarea + id: results + attributes: + label: Results + description: Include warm-up policy, run count, median tok/s, and profile timings when available. + render: text + validations: + required: true + - type: textarea + id: comparison + attributes: + label: Baseline comparison + description: For regressions, provide the last known-good commit and its result. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..3259f41 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,14 @@ +## Summary + +Describe the problem and the smallest change that solves it. + +## Validation + +- [ ] `make -C c check` +- [ ] CUDA changes were tested with `make -C c cuda-test` (if applicable) +- [ ] Performance claims include hardware, commands, and repeatable measurements + +## Compatibility + +- [ ] The default CPU build remains dependency-free +- [ ] No model files, generated binaries, or benchmark artifacts are included diff --git a/.gitignore b/.gitignore index 2132b6f..e68f633 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ c/iobench c/tok_test c/backend_cuda.o c/backend_cuda_test +c/tests/test_json +c/tests/test_st # oracoli tiny generati (make_glm_oracle.py) e dati benchmark scaricati c/glm_tiny/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e215c76 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing + +Keep changes focused and preserve Colibri's dependency-free default CPU path. + +## Local checks + +Run the lightweight checks locally: + +```sh +make -C c check +``` + +This performs one portable CPU build, C unit tests, and Python standard-library +tests. It does not download a model or require CUDA. + +CUDA changes should additionally be checked on a CUDA-capable Linux host: + +```sh +make -C c cuda-test CUDA_ARCH=native +``` + +Benchmark reports should include the commit, exact commands, hardware and +storage details, warm-up policy, run count, and median throughput. diff --git a/c/Makefile b/c/Makefile index d3cb210..04101df 100644 --- a/c/Makefile +++ b/c/Makefile @@ -35,7 +35,9 @@ CUDA_HOME ?= /usr/local/cuda NVCC ?= $(CUDA_HOME)/bin/nvcc CUDA_ARCH ?= native NVCCFLAGS ?= -O3 -std=c++17 -arch=$(CUDA_ARCH) -Xcompiler=-Wall,-Wextra +PYTHON ?= python3 CUDA_OBJ = +TEST_BINS = tests/test_json tests/test_st ifeq ($(CUDA),1) ifeq ($(UNAME_S),Darwin) $(error CUDA=1 is supported only on Linux) @@ -66,5 +68,28 @@ olmoe: olmoe.c st.h json.h portable: $(MAKE) glm ARCH=x86-64-v3 +tests/test_json: tests/test_json.c json.h + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) + +tests/test_st: tests/test_st.c st.h json.h compat.h + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) + +test-c: $(TEST_BINS) + @for test in $(TEST_BINS); do ./$$test || exit 1; done + +test-python: + $(PYTHON) -m unittest discover -s tests -p 'test_*.py' + +test: test-c test-python + +# CI-friendly validation: one portable CPU build and dependency-free tests. +check: + $(MAKE) clean + $(MAKE) portable + $(MAKE) test + clean: - rm -f olmoe glm backend_cuda.o backend_cuda_test + rm -f olmoe glm backend_cuda.o backend_cuda_test $(TEST_BINS) + rm -rf tests/__pycache__ + +.PHONY: all cuda-test portable test-c test-python test check clean diff --git a/c/benchmark_cuda_fixture.py b/c/benchmark_cuda_fixture.py index 5b52258..73a30db 100644 --- a/c/benchmark_cuda_fixture.py +++ b/c/benchmark_cuda_fixture.py @@ -17,15 +17,20 @@ PROFILE_RE = re.compile( PROFILE_KEYS = ("disk", "expert_matmul", "attention", "lm_head", "other") +def parse_output(stdout: str, stderr: str = "") -> tuple[float, list[float]]: + """Extract throughput and profile timings from one engine run.""" + speed = SPEED_RE.search(stdout) + profile = PROFILE_RE.search(stdout) + if not speed or not profile: + raise RuntimeError(f"benchmark output missing\nstdout:\n{stdout}\nstderr:\n{stderr}") + return float(speed.group(1)), [float(value) for value in profile.groups()] + + def execute(engine: str, env: dict[str, str]) -> tuple[float, list[float]]: run = subprocess.run( [engine, "4", "4", "4"], env=env, text=True, capture_output=True, check=True ) - speed = SPEED_RE.search(run.stdout) - profile = PROFILE_RE.search(run.stdout) - if not speed or not profile: - raise RuntimeError(f"benchmark output missing\nstdout:\n{run.stdout}\nstderr:\n{run.stderr}") - return float(speed.group(1)), [float(value) for value in profile.groups()] + return parse_output(run.stdout, run.stderr) def main() -> None: diff --git a/c/tests/test_benchmark_cuda_fixture.py b/c/tests/test_benchmark_cuda_fixture.py new file mode 100644 index 0000000..4ff34dd --- /dev/null +++ b/c/tests/test_benchmark_cuda_fixture.py @@ -0,0 +1,24 @@ +import unittest + +from benchmark_cuda_fixture import parse_output + + +SAMPLE = """ +REPLAY decode: 4 tokens | 12.34 tok/s +PROFILO: expert-disk 1.25s | expert-matmul 2.50s | attention 0.75s | lm_head 0.10s | altro -0.05s +""" + + +class ParseOutputTest(unittest.TestCase): + def test_extracts_speed_and_profile(self): + speed, profile = parse_output(SAMPLE) + self.assertEqual(speed, 12.34) + self.assertEqual(profile, [1.25, 2.5, 0.75, 0.1, -0.05]) + + def test_rejects_incomplete_output(self): + with self.assertRaisesRegex(RuntimeError, "benchmark output missing"): + parse_output("REPLAY decode: 4 tokens | 12.34 tok/s", "engine failed") + + +if __name__ == "__main__": + unittest.main() diff --git a/c/tests/test_json.c b/c/tests/test_json.c new file mode 100644 index 0000000..56941a9 --- /dev/null +++ b/c/tests/test_json.c @@ -0,0 +1,36 @@ +#include +#include +#include + +#include "../json.h" + +#define CHECK(condition) do { \ + if (!(condition)) { \ + fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, __LINE__, #condition); \ + return 1; \ + } \ +} while (0) + +int main(void) { + jval *root = json_parse( + "{\"name\":\"Colibri\\nCPU\",\"enabled\":true,\"empty\":null," + "\"values\":[1,-2.5,3e2],\"unicode\":\"\\u03bb \\uD83D\\uDE80\"}", + NULL + ); + + CHECK(root && root->t == J_OBJ); + CHECK(strcmp(json_get(root, "name")->str, "Colibri\nCPU") == 0); + CHECK(json_get(root, "enabled")->boolean == 1); + CHECK(json_get(root, "empty")->t == J_NULL); + CHECK(json_get(root, "missing") == NULL); + + jval *values = json_get(root, "values"); + CHECK(values->t == J_ARR && values->len == 3); + CHECK(values->kids[0]->num == 1.0); + CHECK(values->kids[1]->num == -2.5); + CHECK(values->kids[2]->num == 300.0); + CHECK(strcmp(json_get(root, "unicode")->str, "λ 🚀") == 0); + + puts("json tests: ok"); + return 0; +} diff --git a/c/tests/test_st.c b/c/tests/test_st.c new file mode 100644 index 0000000..7245c8a --- /dev/null +++ b/c/tests/test_st.c @@ -0,0 +1,26 @@ +#include +#include +#include + +#include "../st.h" + +#define CHECK(condition) do { \ + if (!(condition)) { \ + fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, __LINE__, #condition); \ + return 1; \ + } \ +} while (0) + +int main(void) { + CHECK(bf16_to_f32(0x3f80) == 1.0f); + CHECK(bf16_to_f32(0xc020) == -2.5f); + CHECK(f16_to_f32(0x3c00) == 1.0f); + CHECK(f16_to_f32(0xc100) == -2.5f); + CHECK(f16_to_f32(0x0001) > 0.0f); + CHECK(isinf(f16_to_f32(0x7c00))); + CHECK(st_hash("tensor.weight") == st_hash("tensor.weight")); + CHECK(st_hash("tensor.weight") != st_hash("tensor.bias")); + + puts("safetensors primitive tests: ok"); + return 0; +}