From e78c022215e81b1f1538043c96bbe723622446d7 Mon Sep 17 00:00:00 2001 From: Lester Solbakken Date: Wed, 8 Jun 2022 11:07:53 +0200 Subject: Set short tensor format as default --- .../main/java/com/yahoo/search/query/Presentation.java | 4 ++-- .../com/yahoo/search/rendering/JsonRendererTestCase.java | 8 ++++---- .../ai/vespa/models/handler/ModelsEvaluationHandler.java | 6 +++--- .../models/handler/ModelsEvaluationHandlerTest.java | 16 +++++++++------- .../document/restapi/resource/DocumentV1ApiHandler.java | 2 +- .../java/com/yahoo/tensor/serialization/JsonFormat.java | 14 +++++++++----- 6 files changed, 28 insertions(+), 22 deletions(-) diff --git a/container-search/src/main/java/com/yahoo/search/query/Presentation.java b/container-search/src/main/java/com/yahoo/search/query/Presentation.java index 31641b5c2f0..afa87eb4a06 100644 --- a/container-search/src/main/java/com/yahoo/search/query/Presentation.java +++ b/container-search/src/main/java/com/yahoo/search/query/Presentation.java @@ -75,7 +75,7 @@ public class Presentation implements Cloneable { private boolean timing = false; /** Whether to renders tensors in short form */ - private boolean tensorShortForm = false; + private boolean tensorShortForm = true; /** Set of explicitly requested summary fields, instead of summary classes */ private Set summaryFields = LazySet.newHashSet(); @@ -180,7 +180,7 @@ public class Presentation implements Cloneable { * Returns whether tensors should use short form in JSON and textual representations, see * https://docs.vespa.ai/en/reference/document-json-format.html#tensor * and https://docs.vespa.ai/en/reference/tensor.html#tensor-literal-form. - * Default is false. + * Default is true. */ public boolean getTensorShortForm() { return tensorShortForm; } diff --git a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java index 4a1ab693151..0a4dcb2eec1 100644 --- a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java +++ b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java @@ -204,14 +204,14 @@ public class JsonRendererTestCase { + " \"object\": \"thingie\"," + " \"string\": \"stuff\"," + " \"predicate\": \"a in [b]\"," - + " \"tensor1\": { \"cells\": [ { \"address\": {\"x\": \"a\"}, \"value\":2.0 } ] }," + + " \"tensor1\": { \"type\": \"tensor(x{})\", \"cells\": { \"a\":2.0 } }," + " \"tensor2\": { \"cells\": [] }," - + " \"tensor3\": { \"cells\": [ { \"address\": {\"x\": \"a\", \"y\": \"0\"}, \"value\":2.0 }, { \"address\": {\"x\": \"a\", \"y\": \"1\"}, \"value\":-1.0 } ] }," + + " \"tensor3\": { \"type\": \"tensor(x{},y{})\", \"cells\": [ { \"address\": {\"x\": \"a\", \"y\": \"0\"}, \"value\":2.0 }, { \"address\": {\"x\": \"a\", \"y\": \"1\"}, \"value\":-1.0 } ] }," + " \"summaryfeatures\": {" + " \"scalar1\":1.5," + " \"scalar2\":2.5," - + " \"tensor1\":{\"type\":\"tensor(x[3])\",\"cells\":[{\"address\":{\"x\":\"0\"},\"value\":1.5},{\"address\":{\"x\":\"1\"},\"value\":2.0},{\"address\":{\"x\":\"2\"},\"value\":2.5}]}," - + " \"tensor2\":{\"type\":\"tensor()\",\"cells\":[{\"address\":{},\"value\":0.5}]}" + + " \"tensor1\":{\"type\":\"tensor(x[3])\", \"values\":[1.5, 2.0, 2.5] }," + + " \"tensor2\":{\"type\":\"tensor()\", \"values\":[0.5] }" + " }," + " \"data\": \"Data \\\\xc3\\\\xa6 \\\\xc3\\\\xa5\"" + " }," diff --git a/model-evaluation/src/main/java/ai/vespa/models/handler/ModelsEvaluationHandler.java b/model-evaluation/src/main/java/ai/vespa/models/handler/ModelsEvaluationHandler.java index b0e2be26f8a..2661b9c2eb2 100644 --- a/model-evaluation/src/main/java/ai/vespa/models/handler/ModelsEvaluationHandler.java +++ b/model-evaluation/src/main/java/ai/vespa/models/handler/ModelsEvaluationHandler.java @@ -90,13 +90,13 @@ public class ModelsEvaluationHandler extends ThreadedHttpRequestHandler { Tensor result = evaluator.evaluate(); Optional format = property(request, "format.tensors"); - if (format.isPresent() && format.get().equalsIgnoreCase("short")) { - return new Response(200, JsonFormat.encodeShortForm(result)); + if (format.isPresent() && format.get().equalsIgnoreCase("long")) { + return new Response(200, JsonFormat.encode(result)); } else if (format.isPresent() && format.get().equalsIgnoreCase("string")) { return new Response(200, result.toString().getBytes(StandardCharsets.UTF_8)); } - return new Response(200, JsonFormat.encode(result)); + return new Response(200, JsonFormat.encodeShortForm(result)); } private HttpResponse listAllModels(HttpRequest request) { diff --git a/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java b/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java index 3000c83b7ec..7790f8a60d0 100644 --- a/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java +++ b/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java @@ -66,7 +66,7 @@ public class ModelsEvaluationHandlerTest { @Test public void testXgBoostEvaluationWithoutBindings() { String url = "http://localhost/model-evaluation/v1/xgboost_2_2/eval"; // only has a single function - String expected = "{\"cells\":[{\"address\":{},\"value\":-4.376589999999999}]}"; + String expected = "{\"type\":\"tensor()\",\"values\":[-4.376589999999999]}"; handler.assertResponse(url, 200, expected); } @@ -79,7 +79,7 @@ public class ModelsEvaluationHandlerTest { properties.put("f109", "0.4"); properties.put("non-existing-binding", "-1"); String url = "http://localhost/model-evaluation/v1/xgboost_2_2/eval"; - String expected = "{\"cells\":[{\"address\":{},\"value\":-7.936679999999999}]}"; + String expected = "{\"type\":\"tensor()\",\"values\":[-7.936679999999999]}"; handler.assertResponse(url, properties, 200, expected); } @@ -91,6 +91,7 @@ public class ModelsEvaluationHandlerTest { properties.put("f60", "0.3"); properties.put("f109", "0.4"); properties.put("non-existing-binding", "-1"); + properties.put("format.tensors", "long"); String url = "http://localhost/model-evaluation/v1/xgboost_2_2/eval"; String expected = "{\"cells\":[{\"address\":{},\"value\":-7.936679999999999}]}"; handler.assertResponse(url, properties, 200, expected); @@ -99,7 +100,7 @@ public class ModelsEvaluationHandlerTest { @Test public void testLightGBMEvaluationWithoutBindings() { String url = "http://localhost/model-evaluation/v1/lightgbm_regression/eval"; - String expected = "{\"cells\":[{\"address\":{},\"value\":1.9130086820218188}]}"; + String expected = "{\"type\":\"tensor()\",\"values\":[1.9130086820218188]}"; handler.assertResponse(url, 200, expected); } @@ -112,7 +113,7 @@ public class ModelsEvaluationHandlerTest { properties.put("categorical_2", "i"); properties.put("non-existing-binding", "-1"); String url = "http://localhost/model-evaluation/v1/lightgbm_regression/eval"; - String expected = "{\"cells\":[{\"address\":{},\"value\":2.054697758469921}]}"; + String expected = "{\"type\":\"tensor()\",\"values\":[2.054697758469921]}"; handler.assertResponse(url, properties, 200, expected); } @@ -125,7 +126,7 @@ public class ModelsEvaluationHandlerTest { properties.put("categorical_2", "j"); properties.put("non-existing-binding", "-1"); String url = "http://localhost/model-evaluation/v1/lightgbm_regression/eval"; - String expected = "{\"cells\":[{\"address\":{},\"value\":2.0745534018208094}]}"; + String expected = "{\"type\":\"tensor()\",\"values\":[2.0745534018208094]}"; handler.assertResponse(url, properties, 200, expected); } @@ -162,7 +163,7 @@ public class ModelsEvaluationHandlerTest { Map properties = new HashMap<>(); properties.put("Placeholder", inputTensor()); String url = "http://localhost/model-evaluation/v1/mnist_softmax/eval"; - String expected = "{\"cells\":[{\"address\":{\"d0\":\"0\",\"d1\":\"0\"},\"value\":-0.3546536862850189},{\"address\":{\"d0\":\"0\",\"d1\":\"1\"},\"value\":0.3759574592113495},{\"address\":{\"d0\":\"0\",\"d1\":\"2\"},\"value\":0.06054411828517914},{\"address\":{\"d0\":\"0\",\"d1\":\"3\"},\"value\":-0.251544713973999},{\"address\":{\"d0\":\"0\",\"d1\":\"4\"},\"value\":0.017951013520359993},{\"address\":{\"d0\":\"0\",\"d1\":\"5\"},\"value\":1.2899067401885986},{\"address\":{\"d0\":\"0\",\"d1\":\"6\"},\"value\":-0.10389615595340729},{\"address\":{\"d0\":\"0\",\"d1\":\"7\"},\"value\":0.6367976665496826},{\"address\":{\"d0\":\"0\",\"d1\":\"8\"},\"value\":-1.4136744737625122},{\"address\":{\"d0\":\"0\",\"d1\":\"9\"},\"value\":-0.2573896050453186}]}"; + String expected = "{\"type\":\"tensor(d0[],d1[10])\",\"values\":[[-0.3546536862850189,0.3759574592113495,0.06054411828517914,-0.251544713973999,0.017951013520359993,1.2899067401885986,-0.10389615595340729,0.6367976665496826,-1.4136744737625122,-0.2573896050453186]]}"; handler.assertResponse(url, properties, 200, expected); } @@ -171,7 +172,7 @@ public class ModelsEvaluationHandlerTest { Map properties = new HashMap<>(); properties.put("Placeholder", inputTensor()); String url = "http://localhost/model-evaluation/v1/mnist_softmax/default.add/eval"; - String expected = "{\"cells\":[{\"address\":{\"d0\":\"0\",\"d1\":\"0\"},\"value\":-0.3546536862850189},{\"address\":{\"d0\":\"0\",\"d1\":\"1\"},\"value\":0.3759574592113495},{\"address\":{\"d0\":\"0\",\"d1\":\"2\"},\"value\":0.06054411828517914},{\"address\":{\"d0\":\"0\",\"d1\":\"3\"},\"value\":-0.251544713973999},{\"address\":{\"d0\":\"0\",\"d1\":\"4\"},\"value\":0.017951013520359993},{\"address\":{\"d0\":\"0\",\"d1\":\"5\"},\"value\":1.2899067401885986},{\"address\":{\"d0\":\"0\",\"d1\":\"6\"},\"value\":-0.10389615595340729},{\"address\":{\"d0\":\"0\",\"d1\":\"7\"},\"value\":0.6367976665496826},{\"address\":{\"d0\":\"0\",\"d1\":\"8\"},\"value\":-1.4136744737625122},{\"address\":{\"d0\":\"0\",\"d1\":\"9\"},\"value\":-0.2573896050453186}]}"; + String expected = "{\"type\":\"tensor(d0[],d1[10])\",\"values\":[[-0.3546536862850189,0.3759574592113495,0.06054411828517914,-0.251544713973999,0.017951013520359993,1.2899067401885986,-0.10389615595340729,0.6367976665496826,-1.4136744737625122,-0.2573896050453186]]}"; handler.assertResponse(url, properties, 200, expected); } @@ -179,6 +180,7 @@ public class ModelsEvaluationHandlerTest { public void testMnistSoftmaxEvaluateSpecificFunctionWithBindingsShortForm() { Map properties = new HashMap<>(); properties.put("Placeholder", inputTensorShortForm()); + properties.put("format.tensors", "long"); String url = "http://localhost/model-evaluation/v1/mnist_softmax/default.add/eval"; String expected = "{\"cells\":[{\"address\":{\"d0\":\"0\",\"d1\":\"0\"},\"value\":-0.3546536862850189},{\"address\":{\"d0\":\"0\",\"d1\":\"1\"},\"value\":0.3759574592113495},{\"address\":{\"d0\":\"0\",\"d1\":\"2\"},\"value\":0.06054411828517914},{\"address\":{\"d0\":\"0\",\"d1\":\"3\"},\"value\":-0.251544713973999},{\"address\":{\"d0\":\"0\",\"d1\":\"4\"},\"value\":0.017951013520359993},{\"address\":{\"d0\":\"0\",\"d1\":\"5\"},\"value\":1.2899067401885986},{\"address\":{\"d0\":\"0\",\"d1\":\"6\"},\"value\":-0.10389615595340729},{\"address\":{\"d0\":\"0\",\"d1\":\"7\"},\"value\":0.6367976665496826},{\"address\":{\"d0\":\"0\",\"d1\":\"8\"},\"value\":-1.4136744737625122},{\"address\":{\"d0\":\"0\",\"d1\":\"9\"},\"value\":-0.2573896050453186}]}"; handler.assertResponse(url, properties, 200, expected); diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java index 8c2e39d595e..e7b58bf083d 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java @@ -716,7 +716,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { synchronized void writeSingleDocument(Document document) throws IOException { boolean tensorShortForm = false; if (request != null && request.parameters().containsKey("format.tensors")) { - tensorShortForm = request.parameters().get("format.tensors").contains("short"); + tensorShortForm = !(request.parameters().get("format.tensors").contains("long")); } new JsonWriter(json, tensorShortForm).writeFields(document); } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/serialization/JsonFormat.java b/vespajlib/src/main/java/com/yahoo/tensor/serialization/JsonFormat.java index d7758f309db..b683519988c 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/serialization/JsonFormat.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/serialization/JsonFormat.java @@ -109,11 +109,15 @@ public class JsonFormat { private static void encodeValues(IndexedTensor tensor, Cursor cursor, long[] indexes, int dimension) { DimensionSizes sizes = tensor.dimensionSizes(); - for (indexes[dimension] = 0; indexes[dimension] < sizes.size(dimension); ++indexes[dimension]) { - if (dimension < (sizes.dimensions() - 1)) { - encodeValues(tensor, cursor.addArray(), indexes, dimension + 1); - } else { - cursor.addDouble(tensor.get(indexes)); + if (indexes.length == 0) { + cursor.addDouble(tensor.get(0)); + } else { + for (indexes[dimension] = 0; indexes[dimension] < sizes.size(dimension); ++indexes[dimension]) { + if (dimension < (sizes.dimensions() - 1)) { + encodeValues(tensor, cursor.addArray(), indexes, dimension + 1); + } else { + cursor.addDouble(tensor.get(indexes)); + } } } } -- cgit v1.2.3 From 61d512485d2251a9937b2a5aa1b2327228495077 Mon Sep 17 00:00:00 2001 From: Lester Solbakken Date: Wed, 8 Jun 2022 14:19:44 +0200 Subject: Test short format in document/v1 --- .../restapi/resource/DocumentV1ApiHandler.java | 17 +++++++++------ vespaclient-container-plugin/src/test/cfg/music.sd | 1 + .../restapi/resource/DocumentV1ApiTest.java | 24 +++++++++++++++------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java index e7b58bf083d..626d4f57c58 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java @@ -713,12 +713,17 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { } } - synchronized void writeSingleDocument(Document document) throws IOException { - boolean tensorShortForm = false; - if (request != null && request.parameters().containsKey("format.tensors")) { - tensorShortForm = !(request.parameters().get("format.tensors").contains("long")); + private boolean tensorShortForm() { + if (request != null && + request.parameters().containsKey("format.tensors") && + request.parameters().get("format.tensors").contains("long")) { + return false; } - new JsonWriter(json, tensorShortForm).writeFields(document); + return true; // default + } + + synchronized void writeSingleDocument(Document document) throws IOException { + new JsonWriter(json, tensorShortForm()).writeFields(document); } synchronized void writeDocumentsArrayStart() throws IOException { @@ -737,7 +742,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { ByteArrayOutputStream myOut = new ByteArrayOutputStream(1); myOut.write(','); // Prepend rather than append, to avoid double memory copying. try (JsonGenerator myJson = jsonFactory.createGenerator(myOut)) { - new JsonWriter(myJson).write(document); + new JsonWriter(myJson, tensorShortForm()).write(document); } docs.add(myOut); diff --git a/vespaclient-container-plugin/src/test/cfg/music.sd b/vespaclient-container-plugin/src/test/cfg/music.sd index 75a1918de5b..86597379dd3 100644 --- a/vespaclient-container-plugin/src/test/cfg/music.sd +++ b/vespaclient-container-plugin/src/test/cfg/music.sd @@ -2,5 +2,6 @@ search music { document music { field artist type string { } + field embedding type tensor(x[3]) {} } } diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java index bdb6c6d5580..74a86b6a7b7 100644 --- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java +++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java @@ -14,6 +14,7 @@ import com.yahoo.document.FixedBucketSpaces; import com.yahoo.document.TestAndSetCondition; import com.yahoo.document.config.DocumentmanagerConfig; import com.yahoo.document.datatypes.StringFieldValue; +import com.yahoo.document.datatypes.TensorFieldValue; import com.yahoo.document.restapi.DocumentOperationExecutorConfig; import com.yahoo.document.restapi.resource.DocumentV1ApiHandler.StorageCluster; import com.yahoo.document.update.FieldUpdate; @@ -51,6 +52,7 @@ import com.yahoo.schema.derived.Deriver; import com.yahoo.slime.Inspector; import com.yahoo.slime.JsonFormat; import com.yahoo.slime.SlimeUtils; +import com.yahoo.tensor.Tensor; import com.yahoo.test.ManualClock; import com.yahoo.vdslib.VisitorStatistics; import com.yahoo.vespa.config.content.AllClustersBucketSpacesConfig; @@ -118,7 +120,9 @@ public class DocumentV1ApiTest { final Document doc3 = new Document(manager.getDocumentType("music"), "id:space:music:g=a:three"); { doc1.setFieldValue("artist", "Tom Waits"); + doc1.setFieldValue("embedding", new TensorFieldValue(Tensor.from("tensor(x[3]):[1,2,3]"))); doc2.setFieldValue("artist", "Asa-Chan & Jun-Ray"); + doc2.setFieldValue("embedding", new TensorFieldValue(Tensor.from("tensor(x[3]):[4,5,6]"))); } final Map clusters = Map.of("content", new StorageCluster("content", @@ -229,13 +233,15 @@ public class DocumentV1ApiTest { " {" + " \"id\": \"id:space:music::one\"," + " \"fields\": {" + - " \"artist\": \"Tom Waits\"" + + " \"artist\": \"Tom Waits\", " + + " \"embedding\": { \"type\": \"tensor(x[3])\", \"values\": [1.0,2.0,3.0] } " + " }" + " }," + " {" + " \"id\": \"id:space:music:n=1:two\"," + " \"fields\": {" + - " \"artist\": \"Asa-Chan & Jun-Ray\"" + + " \"artist\": \"Asa-Chan & Jun-Ray\", " + + " \"embedding\": { \"type\": \"tensor(x[3])\", \"values\": [4.0,5.0,6.0] } " + " }" + " }," + " {" + @@ -278,13 +284,15 @@ public class DocumentV1ApiTest { " {" + " \"id\": \"id:space:music::one\"," + " \"fields\": {" + - " \"artist\": \"Tom Waits\"" + + " \"artist\": \"Tom Waits\"," + + " \"embedding\": { \"type\": \"tensor(x[3])\", \"values\": [1.0,2.0,3.0] } " + " }" + " }," + " {" + " \"id\": \"id:space:music:n=1:two\"," + " \"fields\": {" + - " \"artist\": \"Asa-Chan & Jun-Ray\"" + + " \"artist\": \"Asa-Chan & Jun-Ray\"," + + " \"embedding\": { \"type\": \"tensor(x[3])\", \"values\": [4.0,5.0,6.0] } " + " }" + " }" + " ]," + @@ -491,12 +499,13 @@ public class DocumentV1ApiTest { parameters.responseHandler().get().handleResponse(new DocumentResponse(0, doc1)); return new Result(); }); - response = driver.sendRequest("http://localhost/document/v1/space/music/docid/one?"); + response = driver.sendRequest("http://localhost/document/v1/space/music/docid/one?format.tensors=long"); assertSameJson("{" + " \"pathId\": \"/document/v1/space/music/docid/one\"," + " \"id\": \"id:space:music::one\"," + " \"fields\": {" + - " \"artist\": \"Tom Waits\"" + + " \"artist\": \"Tom Waits\"," + + " \"embedding\": { \"cells\": [{\"address\":{\"x\":\"0\"},\"value\":1.0},{\"address\":{\"x\":\"1\"},\"value\": 2.0},{\"address\":{\"x\":\"2\"},\"value\": 3.0}]}" + " }" + "}", response.readAll()); assertEquals(200, response.getStatus()); @@ -532,7 +541,8 @@ public class DocumentV1ApiTest { response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two?condition=test%20it&tracelevel=9", POST, "{" + " \"fields\": {" + - " \"artist\": \"Asa-Chan & Jun-Ray\"" + + " \"artist\": \"Asa-Chan & Jun-Ray\"," + + " \"embedding\": { \"values\": [4.0,5.0,6.0] } " + " }" + "}"); assertSameJson("{" + -- cgit v1.2.3