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
This commit is contained in:
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -11,6 +11,8 @@ c/iobench
|
|||||||
c/tok_test
|
c/tok_test
|
||||||
c/backend_cuda.o
|
c/backend_cuda.o
|
||||||
c/backend_cuda_test
|
c/backend_cuda_test
|
||||||
|
c/tests/test_json
|
||||||
|
c/tests/test_st
|
||||||
|
|
||||||
# oracoli tiny generati (make_glm_oracle.py) e dati benchmark scaricati
|
# oracoli tiny generati (make_glm_oracle.py) e dati benchmark scaricati
|
||||||
c/glm_tiny/
|
c/glm_tiny/
|
||||||
|
|||||||
@@ -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.
|
||||||
+26
-1
@@ -35,7 +35,9 @@ CUDA_HOME ?= /usr/local/cuda
|
|||||||
NVCC ?= $(CUDA_HOME)/bin/nvcc
|
NVCC ?= $(CUDA_HOME)/bin/nvcc
|
||||||
CUDA_ARCH ?= native
|
CUDA_ARCH ?= native
|
||||||
NVCCFLAGS ?= -O3 -std=c++17 -arch=$(CUDA_ARCH) -Xcompiler=-Wall,-Wextra
|
NVCCFLAGS ?= -O3 -std=c++17 -arch=$(CUDA_ARCH) -Xcompiler=-Wall,-Wextra
|
||||||
|
PYTHON ?= python3
|
||||||
CUDA_OBJ =
|
CUDA_OBJ =
|
||||||
|
TEST_BINS = tests/test_json tests/test_st
|
||||||
ifeq ($(CUDA),1)
|
ifeq ($(CUDA),1)
|
||||||
ifeq ($(UNAME_S),Darwin)
|
ifeq ($(UNAME_S),Darwin)
|
||||||
$(error CUDA=1 is supported only on Linux)
|
$(error CUDA=1 is supported only on Linux)
|
||||||
@@ -66,5 +68,28 @@ olmoe: olmoe.c st.h json.h
|
|||||||
portable:
|
portable:
|
||||||
$(MAKE) glm ARCH=x86-64-v3
|
$(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:
|
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
|
||||||
|
|||||||
@@ -17,15 +17,20 @@ PROFILE_RE = re.compile(
|
|||||||
PROFILE_KEYS = ("disk", "expert_matmul", "attention", "lm_head", "other")
|
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]]:
|
def execute(engine: str, env: dict[str, str]) -> tuple[float, list[float]]:
|
||||||
run = subprocess.run(
|
run = subprocess.run(
|
||||||
[engine, "4", "4", "4"], env=env, text=True, capture_output=True, check=True
|
[engine, "4", "4", "4"], env=env, text=True, capture_output=True, check=True
|
||||||
)
|
)
|
||||||
speed = SPEED_RE.search(run.stdout)
|
return parse_output(run.stdout, run.stderr)
|
||||||
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()]
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#include <math.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user