summaryrefslogtreecommitdiff
path: root/tests/test-grammar-integration.cpp
diff options
context:
space:
mode:
authorKawrakow <48489457+ikawrakow@users.noreply.github.com>2024-07-27 07:55:01 +0200
committerGitHub <noreply@github.com>2024-07-27 07:55:01 +0200
commit154e0d75fccf1784fe9ff6fd76a630b66563da3d (patch)
tree81ce6dbb5b1900c1aa78a879f0593c694cab9d27 /tests/test-grammar-integration.cpp
parent0684c3e9c70d49323b4fc517128cbe222cab7f96 (diff)
Merge mainline llama.cpp (#3)
* Merging mainline - WIP * Merging mainline - WIP AVX2 and CUDA appear to work. CUDA performance seems slightly (~1-2%) lower as it is so often the case with llama.cpp/ggml after some "improvements" have been made. * Merging mainline - fix Metal * Remove check --------- Co-authored-by: Iwan Kawrakow <iwan.kawrakow@gmail.com>
Diffstat (limited to 'tests/test-grammar-integration.cpp')
-rw-r--r--tests/test-grammar-integration.cpp646
1 files changed, 465 insertions, 181 deletions
diff --git a/tests/test-grammar-integration.cpp b/tests/test-grammar-integration.cpp
index 96f90c01..68f971bf 100644
--- a/tests/test-grammar-integration.cpp
+++ b/tests/test-grammar-integration.cpp
@@ -15,8 +15,6 @@
using json = nlohmann::ordered_json;
-//#define INCLUDE_FAILING_TESTS 1
-
static llama_grammar* build_grammar(const std::string & grammar_str) {
auto parsed_grammar = grammar_parser::parse(grammar_str.c_str());
@@ -36,31 +34,36 @@ static llama_grammar* build_grammar(const std::string & grammar_str) {
static bool test_build_grammar_fails(const std::string & grammar_str) {
fprintf(stderr, "⚫ Testing failure for grammar: %s\n", grammar_str.c_str());
bool grammar_fails = false;
- try {
- build_grammar(grammar_str);
+ llama_grammar * grammar = build_grammar(grammar_str);
+ if (grammar != nullptr) {
fprintf(stderr, " ❌ Expected build failure, but succeeded\n");
- } catch (const std::exception & err) {
+ } else {
grammar_fails = true;
fprintf(stdout, " ✅︎\n");
}
return grammar_fails;
}
-static bool match_string(const std::string & input, llama_grammar* grammar) {
+static bool match_string(const std::string & input, llama_grammar * grammar) {
auto decoded = decode_utf8(input, {});
const auto & code_points = decoded.first;
+ const llama_grammar_rules & rules = llama_grammar_get_rules (grammar);
+ llama_grammar_stacks & cur_stacks = llama_grammar_get_stacks(grammar);
+
for (auto it = code_points.begin(), end = code_points.end() - 1; it != end; ++it) {
- auto prev_stacks = grammar->stacks;
- llama_grammar_accept(grammar->rules, prev_stacks, *it, grammar->stacks);
- if (grammar->stacks.empty()) {
+ const llama_grammar_stacks prev_stacks = llama_grammar_get_stacks(grammar); // copy
+
+ llama_grammar_accept(rules, prev_stacks, *it, cur_stacks);
+
+ if (cur_stacks.empty()) {
// no stacks means that the grammar failed to match at this point
return false;
}
}
- for (const auto & stack : grammar->stacks) {
+ for (const auto & stack : cur_stacks) {
if (stack.empty()) {
// An empty stack means that the grammar has been completed
return true;
@@ -77,7 +80,9 @@ static void test(const std::string & test_desc, const std::string & grammar_str,
auto grammar = build_grammar(grammar_str);
// Save the original grammar stacks so that we can reset after every new string we want to test
- auto original_stacks = grammar->stacks;
+ const llama_grammar_stacks original_stacks = llama_grammar_get_stacks(grammar);
+
+ llama_grammar_stacks & cur_stacks = llama_grammar_get_stacks(grammar);
fprintf(stderr, " 🔵 Valid strings:\n");
@@ -114,7 +119,7 @@ static void test(const std::string & test_desc, const std::string & grammar_str,
assert(matched);
// Reset the grammar stacks
- grammar->stacks = original_stacks;
+ cur_stacks = original_stacks;
}
fprintf(stderr, " 🟠 Invalid strings:\n");
@@ -134,7 +139,7 @@ static void test(const std::string & test_desc, const std::string & grammar_str,
assert(!matched);
// Reset the grammar stacks
- grammar->stacks = original_stacks;
+ cur_stacks = original_stacks;
}
// Clean up allocated memory
@@ -148,6 +153,250 @@ static void test_schema(const std::string & test_desc, const std::string & schem
}
static void test_simple_grammar() {
+ test_schema(
+ "min 0",
+ R"""({
+ "type": "integer",
+ "minimum": 0
+ })""",
+ // Passing strings
+ {
+ "0",
+ "10",
+ "12",
+ "10000",
+ },
+ // Failing strings
+ {
+ "-1",
+ "-10",
+ "-10000",
+ "-100000000000000000000000000000000",
+ "100000000000000000000000000000000",
+ "00",
+ "01",
+ "-0",
+ }
+ );
+ test_schema(
+ "min 2",
+ // Schema
+ R"""({
+ "type": "integer",
+ "minimum": 2
+ })""",
+ // Passing strings
+ {
+ "2",
+ "3",
+ "4",
+ "10",
+ "20",
+ "1234567890000000",
+ },
+ // Failing strings
+ {
+ "0",
+ "1",
+ "-1",
+ "-100",
+ "0",
+ "1",
+ "01",
+ "02",
+ "12345678900000000",
+ }
+ );
+ test_schema(
+ "min 456",
+ R"""({
+ "type": "integer",
+ "minimum": 456
+ })""",
+ // Passing strings
+ {
+ "456",
+ "4560",
+ "457",
+ "460",
+ "500",
+ },
+ // Failing strings
+ {
+ "455",
+ "356",
+ "50",
+ "050",
+ "-1",
+ "-456",
+ }
+ );
+ test_schema(
+ "min -123",
+ R"""({
+ "type": "integer",
+ "minimum": -123
+ })""",
+ // Passing strings
+ {
+ "-123",
+ "-122",
+ "-11",
+ "-1",
+ "0",
+ "1",
+ "123",
+ "1234",
+ "2345",
+ },
+ // Failing strings
+ {
+ "-1234",
+ "-124",
+ }
+ );
+
+ test_schema(
+ "max 9999",
+ // Schema
+ R"""({
+ "type": "integer",
+ "maximum": 9999
+ })""",
+ // Passing strings
+ {
+ "-99999",
+ "0",
+ "9999",
+ },
+ // Failing strings
+ {
+ "10000",
+ "99991",
+ }
+ );
+ test_schema(
+ "max -9999",
+ // Schema
+ R"""({
+ "type": "integer",
+ "maximum": -9999
+ })""",
+ // Passing strings
+ {
+ "-10000",
+ "-9999",
+ },
+ // Failing strings
+ {
+ "-9998",
+ "0",
+ "9999",
+ }
+ );
+ test_schema(
+ "min 5 max 30",
+ // Schema
+ R"""({
+ "type": "integer",
+ "minimum": 5,
+ "maximum": 30
+ })""",
+ // Passing strings
+ {
+ "5",
+ "10",
+ "30",
+ },
+ // Failing strings
+ {
+ "05",
+ "4",
+ "-1",
+ "31",
+ "123",
+ "0123",
+ }
+ );
+ test_schema(
+ "min -1 max 1",
+ R"""({
+ "type": "integer",
+ "minimum": -1,
+ "maximum": 1
+ })""",
+ // Passing strings
+ {
+ "-1",
+ "0",
+ "1",
+ },
+ // Failing strings
+ {
+ "-11",
+ "-10",
+ "-2",
+ "2",
+ "10",
+ "11",
+ }
+ );
+ test_schema(
+ "min -123 max 42",
+ R"""({
+ "type": "integer",
+ "minimum": -123,
+ "maximum": 42
+ })""",
+ // Passing strings
+ {
+ "-123",
+ "-122",
+ "-13",
+ "-11",
+ "-2",
+ "-1",
+ "0",
+ "1",
+ "5",
+ "10",
+ "39",
+ "40",
+ "42",
+ },
+ // Failing strings
+ {
+ "-0123",
+ "-124",
+ "-1123",
+ "-200",
+ "43",
+ "123",
+ "0123",
+ }
+ );
+ test_schema(
+ "exclusive min / max",
+ // Schema
+ R"""({
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "exclusiveMaximum": 10000
+ })""",
+ // Passing strings
+ {
+ "1",
+ "9999",
+ },
+ // Failing strings
+ {
+ "0",
+ "01",
+ "10000",
+ "99999",
+ }
+ );
+
// Test case for a simple grammar
test_grammar(
"simple grammar",
@@ -510,7 +759,7 @@ static void test_json_schema() {
)""",
// Passing strings
{
- "{}",
+ R"""({})""",
R"""({"foo": "bar"})""",
},
// Failing strings
@@ -518,7 +767,7 @@ static void test_json_schema() {
"",
"[]",
"null",
- "\"\"",
+ R"""("")""",
"true",
}
);
@@ -526,16 +775,14 @@ static void test_json_schema() {
test_schema(
"exotic formats (list)",
// Schema
- R"""(
- {
+ R"""({
"items": [
{ "format": "date" },
{ "format": "uuid" },
{ "format": "time" },
{ "format": "date-time" }
]
- }
- )""",
+ })""",
// Passing strings
{
// "{}", // NOTE: This string passes for this schema on https://www.jsonschemavalidator.net/ -- should it?
@@ -554,125 +801,113 @@ static void test_json_schema() {
test_schema(
"string",
// Schema
- R"""(
- {
- "type": "string"
- }
- )""",
+ R"""({
+ "type": "string"
+ })""",
// Passing strings
{
- "\"foo\"",
- "\"bar\"",
- "\"\"",
+ R"""("foo")""",
+ R"""("bar")""",
+ R"""("")""",
},
// Failing strings
{
- "{}",
- "\"foo\": \"bar\"",
+ R"""({})""",
+ R"""("foo": "bar")""",
}
);
test_schema(
"string w/ min length 1",
// Schema
- R"""(
- {
- "type": "string",
- "minLength": 1
- }
- )""",
+ R"""({
+ "type": "string",
+ "minLength": 1
+ })""",
// Passing strings
{
- "\"foo\"",
- "\"bar\"",
+ R"""("foo")""",
+ R"""("bar")""",
},
// Failing strings
{
- "\"\"",
- "{}",
- "\"foo\": \"bar\"",
+ R"""("")""",
+ R"""({})""",
+ R"""("foo": "bar")""",
}
);
test_schema(
"string w/ min length 3",
// Schema
- R"""(
- {
+ R"""({
"type": "string",
"minLength": 3
- }
- )""",
+ })""",
// Passing strings
{
- "\"foo\"",
- "\"bar\"",
- "\"foobar\"",
+ R"""("foo")""",
+ R"""("bar")""",
+ R"""("foobar")""",
},
// Failing strings
{
- "\"\"",
- "\"f\"",
- "\"fo\"",
+ R"""("")""",
+ R"""("f")""",
+ R"""("fo")""",
}
);
test_schema(
"string w/ max length",
// Schema
- R"""(
- {
- "type": "string",
- "maxLength": 3
- }
- )""",
+ R"""({
+ "type": "string",
+ "maxLength": 3
+ })""",
// Passing strings
{
- "\"foo\"",
- "\"bar\"",
- "\"\"",
- "\"f\"",
- "\"fo\"",
+ R"""("foo")""",
+ R"""("bar")""",
+ R"""("")""",
+ R"""("f")""",
+ R"""("fo")""",
},
// Failing strings
{
- "\"foobar\"",
+ R"""("foobar")""",
}
);
test_schema(
"string w/ min & max length",
// Schema
- R"""(
- {
- "type": "string",
- "minLength": 1,
- "maxLength": 4
- }
- )""",
+ R"""({
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 4
+ })""",
// Passing strings
{
- "\"foo\"",
- "\"bar\"",
- "\"f\"",
- "\"barf\"",
+ R"""("foo")""",
+ R"""("bar")""",
+ R"""("f")""",
+ R"""("barf")""",
},
// Failing strings
{
- "\"\"",
- "\"barfo\"",
- "\"foobar\"",
+ R"""("")""",
+ R"""("barfo")""",
+ R"""("foobar")""",
}
);
test_schema(
"boolean",
// Schema
- R"""(
- {
- "type": "boolean"
- }
- )""",
+ R"""({
+ "type": "boolean"
+ })""",
// Passing strings
{
"true",
@@ -680,123 +915,171 @@ static void test_json_schema() {
},
// Failing strings
{
- "\"\"",
- "\"true\"",
- "True",
- "FALSE",
+ R"""("")""",
+ R"""("true")""",
+ R"""(True)""",
+ R"""(FALSE)""",
}
);
test_schema(
"integer",
// Schema
- R"""(
- {
- "type": "integer"
- }
- )""",
+ R"""({
+ "type": "integer"
+ })""",
// Passing strings
{
- "0",
- "12345",
- "1234567890123456"
+ R"""(0)""",
+ R"""(12345)""",
+ R"""(1234567890123456)""",
},
// Failing strings
{
- "",
- "01",
- "007",
- "12345678901234567"
+ R"""()""",
+ R"""(01)""",
+ R"""(007)""",
+ R"""(12345678901234567 )""",
}
);
test_schema(
"string const",
// Schema
- R"""(
- {
- "const": "foo"
- }
- )""",
+ R"""({
+ "const": "foo"
+ })""",
// Passing strings
{
- "\"foo\"",
+ R"""("foo")""",
},
// Failing strings
{
- "foo",
- "\"bar\"",
+ R"""(foo)""",
+ R"""("bar")""",
}
);
test_schema(
"non-string const",
// Schema
- R"""(
- {
- "const": true
- }
- )""",
+ R"""({
+ "const": true
+ })""",
// Passing strings
{
- "true",
+ R"""(true)""",
},
// Failing strings
{
- "",
- "foo",
- "\"true\"",
+ R"""()""",
+ R"""(foo)""",
+ R"""("true")""",
}
);
test_schema(
"non-string const",
// Schema
+ R"""({
+ "enum": ["red", "amber", "green", null, 42, ["foo"]]
+ })""",
+ // Passing strings
+ {
+ R"""("red")""",
+ R"""(null)""",
+ R"""(42)""",
+ R"""(["foo"])""",
+ },
+ // Failing strings
+ {
+ R"""()""",
+ R"""(420)""",
+ R"""(true)""",
+ R"""(foo)""",
+ }
+ );
+
+ test_schema(
+ "simple pattern",
+ // Schema
+ R"""({
+ "pattern": "^[a-zA-Z0-9_-]*$"
+ })""",
+ // Passing strings
+ {
+ R"""("")""",
+ R"""("He_llo-12")""",
+ },
+ // Failing strings
+ {
+ R"""("!")""",
+ R"""("Hello World")""",
+ }
+ );
+
+ test_schema(
+ "pattern with escapes",
+ // Schema
+ R"""({
+ "pattern": "^a\\^\\$\\.\\[\\]\\(\\)\\|\\{\\}\\*\\+\\?b$"
+ })""",
+ // Passing strings
+ {
+ R"""("a^$.[]()|{}*+?b")""",
+ },
+ // Failing strings
+ {
+ R"""("ab")""",
+ }
+ );
+
+ test_schema(
+ "",
+ // Schema
R"""(
{
- "enum": ["red", "amber", "green", null, 42, ["foo"]]
+ "type": ["array", "null"],
+ "items": { "type": "string" }
}
)""",
// Passing strings
{
- "\"red\"",
"null",
- "42",
- "[\"foo\"]",
+ "[]",
+ "[\"123\"]",
+ "[\"foo\", \"bar\"]",
},
// Failing strings
{
"",
- "420",
- "true",
- "foo",
+ "[123]",
+ "\"foo\"",
+ "[\"foo\", 42]",
}
);
-
test_schema(
"min+max items",
// Schema
- R"""(
- {
- "items": {
- "type": ["number", "integer"]
- },
- "minItems": 3,
- "maxItems": 5
- }
- )""",
+ R"""({
+ "items": {
+ "type": ["number", "integer"]
+ },
+ "minItems": 3,
+ "maxItems": 5
+ })""",
// Passing strings
{
- "[1, 2, 3]",
- "[1, 2, 3, 4]",
- "[1, 2, 3, 4, 5]",
+ R"""([1, 2, 3])""",
+ R"""([1, 2, 3, 4])""",
+ R"""([1, 2, 3, 4, 5])""",
},
// Failing strings
{
- "[1, 2]",
- "[1, 2, 3, 4, 5, 6]",
- "1"
+ R"""([1, 2])""",
+ R"""([1, 2, 3, 4, 5, 6])""",
+ R"""(1)""",
}
);
@@ -804,16 +1087,14 @@ static void test_json_schema() {
test_schema(
"object properties",
// Schema
- R"""(
- {
+ R"""({
"type": "object",
"properties": {
"number": { "type": "number" },
"street_name": { "type": "string" },
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
}
- }
- )""",
+ })""",
// Passing strings
{
R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue"})""",
@@ -822,13 +1103,7 @@ static void test_json_schema() {
R"""({ "number": 1600, "street_name": "Pennsylvania" })""",
// "By extension, even an empty object is valid"
R"""({})""",
- // "By default, providing additional properties is valid"
-#ifdef INCLUDE_FAILING_TESTS
- // TODO: The following should pass, but currently FAILS. Additional properties should be permitted by default.
- R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue", "direction":"NW"})""",
- // TODO: Spaces should be permitted around enum values, but currently they fail to pass.
R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""",
-#endif
},
// Failing strings
{
@@ -838,16 +1113,41 @@ static void test_json_schema() {
R"""({ "street_name": "Pennsylvania", "number": 1600 })""",
// Reorder properties
R"""({ "number": "1600", "street_name": "Pennsylvania", "street_type":"Avenue"})""",
+ // "Additional properties default to false for generation, even though the spec says true.
+ R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue", "direction":"NW"})""",
+
}
);
+ test_schema(
+ "additional properties can't override other properties",
+ R"""({
+ "properties": {
+ "a": {"type": "integer"},
+ "b": {"type": "integer"}
+ },
+ "additionalProperties": true
+ })""",
+ // Passing strings
+ {
+ R"""({"a": 42})""",
+ R"""({"c": ""})""",
+ R"""({"a": 42, "c": ""})""",
+ R"""({"a_": ""})""",
+ },
+ // Failing strings
+ {
+ R"""()""",
+ R"""({"a": ""})""",
+ R"""({"a": "", "b": ""})""",
+ }
+ );
// Properties (from: https://json-schema.org/understanding-json-schema/reference/object#properties)
test_schema(
"object properties, additionalProperties: true",
// Schema
- R"""(
- {
+ R"""({
"type": "object",
"properties": {
"number": { "type": "number" },
@@ -855,26 +1155,18 @@ static void test_json_schema() {
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
},
"additionalProperties": true
- }
- )""",
+ })""",
// Passing strings
{
// "By extension, even an empty object is valid"
R"""({})""",
-#ifdef INCLUDE_FAILING_TESTS
- // TODO: Following line should pass and doesn't
R"""({"number":1600,"street_name":"Pennsylvania","street_type":"Avenue"})""",
// "By default, leaving out properties is valid"
- // TODO: Following line should pass and doesn't
R"""({ "street_name": "Pennsylvania" })""",
- // TODO: Following line should pass and doesn't
R"""({ "number": 1600, "street_name": "Pennsylvania" })""",
// "By default, providing additional properties is valid"
- // TODO: The following should pass, but currently FAILS. Additional properties should be permitted by default.
R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue", "direction":"NW"})""",
- // TODO: Spaces should be permitted around enum values, but currently they fail to pass.
R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""",
-#endif
},
// Failing strings
{
@@ -889,8 +1181,7 @@ static void test_json_schema() {
test_schema(
"required + optional props each in original order",
// Schema
- R"""(
- {
+ R"""({
"type": "object",
"properties": {
"number": { "type": "number" },
@@ -898,18 +1189,15 @@ static void test_json_schema() {
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
},
"additionalProperties": false
- }
- )""",
+ })""",
// Passing strings
{
R"""({ "street_name": "Pennsylvania" })""",
R"""({ "number": 1600, "street_type":"Avenue"})""",
R"""({ "number": 1600, "street_name": "Pennsylvania" })""",
R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type":"Avenue"})""",
-#ifdef INCLUDE_FAILING_TESTS
- // TODO: Spaces should be permitted around enum values, but currently they fail to pass.
+ // Spaces are permitted around enum values
R"""({ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" })""",
-#endif
},
// Failing strings
{
@@ -923,18 +1211,16 @@ static void test_json_schema() {
test_schema(
"required + optional props each in original order",
// Schema
- R"""(
- {
- "properties": {
- "b": {"type": "string"},
- "a": {"type": "string"},
- "d": {"type": "string"},
- "c": {"type": "string"}
- },
- "required": ["a", "b"],
- "additionalProperties": false
- }
- )""",
+ R"""({
+ "properties": {
+ "b": {"type": "string"},
+ "a": {"type": "string"},
+ "d": {"type": "string"},
+ "c": {"type": "string"}
+ },
+ "required": ["a", "b"],
+ "additionalProperties": false
+ })""",
// Passing strings
{
R"""({"b": "foo", "a": "bar"})""",
@@ -954,8 +1240,7 @@ static void test_json_schema() {
test_schema(
"required props",
// Schema
- R"""(
- {
+ R"""({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"title": "Product",
@@ -1001,8 +1286,7 @@ static void test_json_schema() {
}
},
"required": [ "productId", "productName", "price" ]
- }
- )""",
+ })""",
// Passing strings
{
R"""({"productId": 1, "productName": "A green door", "price": 12.50})""",