From c54157061aba2730cbaa428d2b5b80001efa4a25 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Thu, 28 Jun 2018 20:32:35 +0200 Subject: Model inference initial commit --- .../container/config/StatisticsRequestHandler.java | 4 ++-- .../container/core/config/HandlersConfigurerDi.java | 14 +++++--------- .../container/handler/AccessLogRequestHandler.java | 2 ++ .../yahoo/container/handler/test/MockService.java | 2 -- .../java/com/yahoo/container/di/ConfigRetriever.java | 20 +++++++++++--------- .../main/java/com/yahoo/container/di/Container.java | 9 +++------ .../yahoo/container/jdisc/ConfiguredApplication.java | 15 +++++++-------- .../main/java/com/yahoo/document/datatypes/Raw.java | 13 ++++++++----- .../ai/vespa/models/evaluation/ModelsEvaluator.java | 16 ++++++++++++++++ .../config/RankprofilesConfigImporter.java | 16 ++++++++++++++++ pom.xml | 1 + 11 files changed, 71 insertions(+), 41 deletions(-) create mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java create mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/config/RankprofilesConfigImporter.java diff --git a/container-core/src/main/java/com/yahoo/container/config/StatisticsRequestHandler.java b/container-core/src/main/java/com/yahoo/container/config/StatisticsRequestHandler.java index 1e5c22f4023..e0b6392b64a 100644 --- a/container-core/src/main/java/com/yahoo/container/config/StatisticsRequestHandler.java +++ b/container-core/src/main/java/com/yahoo/container/config/StatisticsRequestHandler.java @@ -20,8 +20,8 @@ import java.util.concurrent.Executor; * Handler of statistics http requests. Temporary hack as a step towards a more * general network interface. * - * @author Steinar Knutsen - * @author Einar M R Rosenvinge + * @author Steinar Knutsen + * @author Einar M R Rosenvinge */ public class StatisticsRequestHandler extends ThreadedHttpRequestHandler { diff --git a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java index e2c6da6fab8..55d7de90f33 100644 --- a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java +++ b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java @@ -86,11 +86,7 @@ public class HandlersConfigurerDi { osgiWrapper = new OsgiWrapper(osgiFramework, vespaContainer.getBundleLoader()); container = new Container(subscriberFactory, configId, deconstructor, osgiWrapper); - try { - getNewComponentGraph(discInjector, false); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted while setting up handlers for the first time."); - } + getNewComponentGraph(discInjector, false); } private static class OsgiWrapper extends OsgiImpl implements com.yahoo.container.di.Osgi { @@ -139,10 +135,10 @@ public class HandlersConfigurerDi { /** * Wait for new config to arrive and produce the new graph */ - public void getNewComponentGraph(Injector discInjector, boolean restartOnRedeploy) throws InterruptedException { - currentGraph = container.getNewComponentGraph(currentGraph, createFallbackInjector(vespaContainer, discInjector), restartOnRedeploy); - - assert (currentGraph.getInstance(RegistriesHack.class) != null); // TODO: Remove, seems quite pointless? + public void getNewComponentGraph(Injector discInjector, boolean restartOnRedeploy) { + currentGraph = container.getNewComponentGraph(currentGraph, + createFallbackInjector(vespaContainer, discInjector), + restartOnRedeploy); } @SuppressWarnings("deprecation") diff --git a/container-core/src/main/java/com/yahoo/container/handler/AccessLogRequestHandler.java b/container-core/src/main/java/com/yahoo/container/handler/AccessLogRequestHandler.java index 241ae269fc9..c50dee43eaf 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/AccessLogRequestHandler.java +++ b/container-core/src/main/java/com/yahoo/container/handler/AccessLogRequestHandler.java @@ -22,6 +22,7 @@ import java.util.concurrent.Executor; * @author dybis */ public class AccessLogRequestHandler extends ThreadedHttpRequestHandler { + private final CircularArrayAccessLogKeeper circularArrayAccessLogKeeper; private final JsonFactory jsonFactory = new JsonFactory(); @@ -53,4 +54,5 @@ public class AccessLogRequestHandler extends ThreadedHttpRequestHandler { } }; } + } diff --git a/container-core/src/main/java/com/yahoo/container/handler/test/MockService.java b/container-core/src/main/java/com/yahoo/container/handler/test/MockService.java index 99d28b9bcf1..7bd18c519eb 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/test/MockService.java +++ b/container-core/src/main/java/com/yahoo/container/handler/test/MockService.java @@ -39,12 +39,10 @@ import java.util.logging.Logger; * for descriptions of the format. * * @author lulf - * @since 5.1.21 */ @Beta public class MockService extends LoggingRequestHandler { - private final static Logger log = Logger.getLogger(MockService.class.getName()); private MockServiceHandler handler; /** diff --git a/container-di/src/main/java/com/yahoo/container/di/ConfigRetriever.java b/container-di/src/main/java/com/yahoo/container/di/ConfigRetriever.java index fe315c0eba5..337997f8ac2 100644 --- a/container-di/src/main/java/com/yahoo/container/di/ConfigRetriever.java +++ b/container-di/src/main/java/com/yahoo/container/di/ConfigRetriever.java @@ -47,13 +47,14 @@ public final class ConfigRetriever { /** * Loop forever until we get config */ - public ConfigSnapshot getConfigs(Set> componentConfigKeys, long leastGeneration, - boolean restartOnRedeploy) { + public ConfigSnapshot getConfigs(Set> componentConfigKeys, + long leastGeneration, + boolean restartOnRedeploy) { while (true) { - if (!Sets.intersection(componentConfigKeys, bootstrapKeys).isEmpty()) { - throw new IllegalArgumentException( - "Component config keys [" + componentConfigKeys + "] overlaps with bootstrap config keys [" + bootstrapKeys + "]"); - } + if (!Sets.intersection(componentConfigKeys, bootstrapKeys).isEmpty()) + throw new IllegalArgumentException("Component config keys [" + componentConfigKeys + + "] overlaps with bootstrap config keys [" + bootstrapKeys + "]"); + log.log(DEBUG, "getConfigs: " + componentConfigKeys); Set> allKeys = new HashSet<>(componentConfigKeys); allKeys.addAll(bootstrapKeys); @@ -75,8 +76,9 @@ public final class ConfigRetriever { /** * Try to get config just once */ - public Optional getConfigsOnce(Set> componentConfigKeys, long leastGeneration, - boolean restartOnRedeploy) { + Optional getConfigsOnce(Set> componentConfigKeys, + long leastGeneration, + boolean restartOnRedeploy) { if (!Sets.intersection(componentConfigKeys, bootstrapKeys).isEmpty()) { throw new IllegalArgumentException( "Component config keys [" + componentConfigKeys + "] overlaps with bootstrap config keys [" + bootstrapKeys + "]"); @@ -114,7 +116,7 @@ public final class ConfigRetriever { } else { // This should not be a normal case, and hence a warning to allow investigation. log.warning("Did not get same generation for bootstrap (" + newestBootstrapGeneration + ") and components configs (" - + newestComponentGeneration + ")."); + + newestComponentGeneration + ")."); return Optional.empty(); } } diff --git a/container-di/src/main/java/com/yahoo/container/di/Container.java b/container-di/src/main/java/com/yahoo/container/di/Container.java index 7c58120d858..fb427bcf8ae 100644 --- a/container-di/src/main/java/com/yahoo/container/di/Container.java +++ b/container-di/src/main/java/com/yahoo/container/di/Container.java @@ -73,7 +73,6 @@ public class Container { } public ComponentGraph getNewComponentGraph(ComponentGraph oldGraph, Injector fallbackInjector, boolean restartOnRedeploy) { - try { ComponentGraph newGraph = getConfigAndCreateGraph(oldGraph, fallbackInjector, restartOnRedeploy); newGraph.reuseNodes(oldGraph); @@ -87,11 +86,11 @@ public class Container { } } - public ComponentGraph getNewComponentGraph(ComponentGraph oldGraph) { + ComponentGraph getNewComponentGraph(ComponentGraph oldGraph) { return getNewComponentGraph(oldGraph, Guice.createInjector(), false); } - public ComponentGraph getNewComponentGraph() { + ComponentGraph getNewComponentGraph() { return getNewComponentGraph(new ComponentGraph(), Guice.createInjector(), false); } @@ -125,10 +124,8 @@ public class Container { } } - public ComponentGraph getConfigAndCreateGraph(ComponentGraph graph, Injector fallbackInjector, boolean restartOnRedeploy) { - + private ComponentGraph getConfigAndCreateGraph(ComponentGraph graph, Injector fallbackInjector, boolean restartOnRedeploy) { ConfigSnapshot snapshot; - while (true) { snapshot = configurer.getConfigs(graph.configKeys(), leastGeneration, restartOnRedeploy); diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java index 932d31c0036..8d54c04d084 100644 --- a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java @@ -203,7 +203,7 @@ public final class ConfiguredApplication implements Application { // Block until new config arrives, and it should be applied configurer.getNewComponentGraph(builder.guiceModules().activate(), qrConfig.restartOnDeploy()); intitializeAndActivateContainer(builder); - } catch (ConfigInterruptedException | InterruptedException e) { + } catch (ConfigInterruptedException e) { break; } catch (Exception | LinkageError e) { // LinkageError: OSGi problems log.log(Level.SEVERE, @@ -256,13 +256,12 @@ public final class ConfiguredApplication implements Application { } private void configureComponents(Injector discInjector) { - configurer = new HandlersConfigurerDi( - subscriberFactory, - Container.get(), - configId, - new Deconstructor(true), - discInjector, - osgiFramework); + configurer = new HandlersConfigurerDi(subscriberFactory, + Container.get(), + configId, + new Deconstructor(true), + discInjector, + osgiFramework); } private void setupGuiceBindings(GuiceRepository modules) { diff --git a/document/src/main/java/com/yahoo/document/datatypes/Raw.java b/document/src/main/java/com/yahoo/document/datatypes/Raw.java index 2a5383705df..23ed0cee23e 100644 --- a/document/src/main/java/com/yahoo/document/datatypes/Raw.java +++ b/document/src/main/java/com/yahoo/document/datatypes/Raw.java @@ -17,15 +17,16 @@ import java.util.Arrays; /** * FieldValue which encapsulates a Raw value * - * @author Einar M R Rosenvinge + * @author Einar M R Rosenvinge */ public final class Raw extends FieldValue { + private static class Factory extends PrimitiveDataType.Factory { public FieldValue create() { return new Raw(); } } - public static PrimitiveDataType.Factory getFactory() { return new Factory(); } + public static final int classId = registerClass(Ids.document + 16, Raw.class); private ByteBuffer value; @@ -42,6 +43,8 @@ public final class Raw extends FieldValue { value.position(0); } + public static PrimitiveDataType.Factory getFactory() { return new Factory(); } + public ByteBuffer getByteBuffer() { return value; } @@ -136,11 +139,11 @@ public final class Raw extends FieldValue { } /* (non-Javadoc) - * @see com.yahoo.document.datatypes.FieldValue#deserialize(com.yahoo.document.Field, com.yahoo.document.serialization.FieldReader) - */ - + * @see com.yahoo.document.datatypes.FieldValue#deserialize(com.yahoo.document.Field, com.yahoo.document.serialization.FieldReader) + */ @Override public void deserialize(Field field, FieldReader reader) { reader.read(field, this); } + } diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java new file mode 100644 index 00000000000..673c7b5e354 --- /dev/null +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java @@ -0,0 +1,16 @@ +package ai.vespa.models.evaluation; + +import com.yahoo.vespa.config.search.RankProfilesConfig; + +/** + * Evaluates machine-learned models added to Vespa applications and available as config form. + * + * @author bratseth + */ +public class ModelsEvaluator { + + public ModelsEvaluator(RankProfilesConfig config) { + new RankProfilesConfigImporter().importFrom(config); + } + +} diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankprofilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankprofilesConfigImporter.java new file mode 100644 index 00000000000..70964529f53 --- /dev/null +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankprofilesConfigImporter.java @@ -0,0 +1,16 @@ +package ai.vespa.models.evaluation.config; + +import com.yahoo.vespa.config.search.RankProfilesConfig; + +/** + * Converts RankprofilesConfig instances to RankingExpressions for evaluation + */ +public class RankprofilesConfigImporter { + + public void importFrom(RankProfilesConfig config) { + for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { + + } + } + +} diff --git a/pom.xml b/pom.xml index 1f766b2878d..9d7b3823347 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,7 @@ messagebus-disc messagebus metrics + model-inference node-repository node-admin node-maintainer -- cgit v1.2.3 From 709934e2992945773e6d9a4c49ad087f78e6fcb4 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Thu, 28 Jun 2018 20:34:35 +0200 Subject: Model inference initial commit --- model-inference/pom.xml | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 model-inference/pom.xml diff --git a/model-inference/pom.xml b/model-inference/pom.xml new file mode 100644 index 00000000000..e17d5545287 --- /dev/null +++ b/model-inference/pom.xml @@ -0,0 +1,73 @@ + + + + 4.0.0 + + com.yahoo.vespa + parent + 6-SNAPSHOT + ../parent/pom.xml + + model-inference + 6-SNAPSHOT + container-plugin + + + junit + junit + test + + + com.yahoo.vespa + component + ${project.version} + provided + + + com.yahoo.vespa + configdefinitions + ${project.version} + provided + + + com.yahoo.vespa + vespajlib + ${project.version} + provided + + + com.yahoo.vespa + searchlib + ${project.version} + provided + + + + + + com.yahoo.vespa + bundle-plugin + true + + + org.apache.maven.plugins + maven-jar-plugin + + + + ${project.artifactId} + ${project.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + -- cgit v1.2.3 From a2a175887d62296af5051a07f0c1dd36cc07c4b0 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Thu, 5 Jul 2018 15:40:32 +0200 Subject: Model inference WIP --- .../com/yahoo/config/subscription/ConfigURI.java | 3 +- .../com/yahoo/config/subscription/FileSource.java | 6 +- .../com/yahoo/vespa/config/ConfigFileFormat.java | 2 +- .../java/com/yahoo/prelude/fastsearch/FastHit.java | 2 +- .../src/main/java/com/yahoo/search/Query.java | 6 +- model-inference/pom.xml | 5 + .../java/ai/vespa/models/evaluation/Model.java | 34 +++ .../vespa/models/evaluation/ModelsEvaluator.java | 4 + .../config/RankProfilesConfigImporter.java | 55 ++++ .../config/RankprofilesConfigImporter.java | 16 - .../models/evaluation/ModelsEvaluatorTest.java | 13 + .../config/RankProfilesImporterTest.java | 51 ++++ .../config/rankexpression/rank-profiles.cfg | 296 +++++++++++++++++++ .../config/rankexpression/rankexpression.sd | 327 +++++++++++++++++++++ 14 files changed, 797 insertions(+), 23 deletions(-) create mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/Model.java create mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/config/RankProfilesConfigImporter.java delete mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/config/RankprofilesConfigImporter.java create mode 100644 model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java create mode 100644 model-inference/src/test/java/ai/vespa/models/evaluation/config/RankProfilesImporterTest.java create mode 100644 model-inference/src/test/resources/config/rankexpression/rank-profiles.cfg create mode 100644 model-inference/src/test/resources/config/rankexpression/rankexpression.sd diff --git a/config/src/main/java/com/yahoo/config/subscription/ConfigURI.java b/config/src/main/java/com/yahoo/config/subscription/ConfigURI.java index 537aff096a9..850dbcc1bf4 100644 --- a/config/src/main/java/com/yahoo/config/subscription/ConfigURI.java +++ b/config/src/main/java/com/yahoo/config/subscription/ConfigURI.java @@ -10,9 +10,9 @@ import com.yahoo.config.subscription.impl.JRTConfigRequester; * object to simplify parameter passing. * * @author lulf - * @since 5.1 */ public class ConfigURI { + private String configId; private ConfigSource source; @@ -54,4 +54,5 @@ public class ConfigURI { public static ConfigURI createFromIdAndSource(String configId, ConfigSource source) { return new ConfigURI(configId, source); } + } diff --git a/config/src/main/java/com/yahoo/config/subscription/FileSource.java b/config/src/main/java/com/yahoo/config/subscription/FileSource.java index d69e2429dbb..27129853eae 100644 --- a/config/src/main/java/com/yahoo/config/subscription/FileSource.java +++ b/config/src/main/java/com/yahoo/config/subscription/FileSource.java @@ -5,15 +5,15 @@ import java.io.File; /** * Source specifying config from one local file - * @author vegardh - * @since 5.1 * + * @author vegardh */ public class FileSource implements ConfigSource { + private final File file; public FileSource(File file) { - if (!file.isFile()) throw new IllegalArgumentException("Not an ordinary file: "+file); + if ( ! file.isFile()) throw new IllegalArgumentException("Not an ordinary file: "+file); this.file = file; } diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigFileFormat.java b/config/src/main/java/com/yahoo/vespa/config/ConfigFileFormat.java index da51df143d2..751f6578575 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigFileFormat.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConfigFileFormat.java @@ -13,9 +13,9 @@ import java.util.Stack; /** * @author lulf - * @since 5.1 */ public class ConfigFileFormat implements SlimeFormat, ObjectTraverser { + private final InnerCNode root; private DataOutputStream out = null; private Stack nodeStack; diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java index 3a2e922f9e0..f6894439ddc 100644 --- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java +++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java @@ -131,7 +131,7 @@ public class FastHit extends Hit { /** Returns the index of the node this hit originated at */ public int getDistributionKey() { return distributionKey; } - /** Returns the index of the node this hit originated at */ + /** Sets the index of the node this hit originated at */ public void setDistributionKey(int distributionKey) { this.distributionKey = distributionKey; } /** diff --git a/container-search/src/main/java/com/yahoo/search/Query.java b/container-search/src/main/java/com/yahoo/search/Query.java index ab6976e29d9..5ec2202d0c3 100644 --- a/container-search/src/main/java/com/yahoo/search/Query.java +++ b/container-search/src/main/java/com/yahoo/search/Query.java @@ -497,11 +497,15 @@ public class Query extends com.yahoo.processing.Request implements Cloneable { * Get the appropriate timeout for the query. * * @return timeout in milliseconds - **/ + */ public long getTimeLeft() { return getTimeout() - getDurationTime(); } + /** + * @deprecated do not use + */ + @Deprecated // TODO: Remove on Vespa 7 public boolean requestHasProperty(String name) { return httpRequest.hasProperty(name); } diff --git a/model-inference/pom.xml b/model-inference/pom.xml index e17d5545287..ad258d1edf4 100644 --- a/model-inference/pom.xml +++ b/model-inference/pom.xml @@ -44,6 +44,11 @@ ${project.version} provided + + com.google.guava + guava + provided + diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java new file mode 100644 index 00000000000..76d990a493e --- /dev/null +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java @@ -0,0 +1,34 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.google.common.collect.ImmutableList; +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; + +import java.util.Collection; +import java.util.List; + +/** + * A named collection of functions + * + * @author bratseth + */ +public class Model { + + private final String name; + + private final ImmutableList functions; + + public Model(String name, Collection functions) { + this.name = name; + this.functions = ImmutableList.copyOf(functions); + } + + public String name() { return name; } + + /** Returns an immutable list of the expression functions of this */ + public List functions() { return functions; } + + @Override + public String toString() { return "Model '" + name + "'"; } + +} diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java index 673c7b5e354..c1a22a361e9 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java @@ -1,5 +1,7 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.models.evaluation; +import ai.vespa.models.evaluation.config.RankProfilesConfigImporter; import com.yahoo.vespa.config.search.RankProfilesConfig; /** @@ -13,4 +15,6 @@ public class ModelsEvaluator { new RankProfilesConfigImporter().importFrom(config); } + + } diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankProfilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankProfilesConfigImporter.java new file mode 100644 index 00000000000..a81006d526e --- /dev/null +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankProfilesConfigImporter.java @@ -0,0 +1,55 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation.config; + +import ai.vespa.models.evaluation.Model; +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.RankingExpression; +import com.yahoo.vespa.config.search.RankProfilesConfig; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Converts RankProfilesConfig instances to RankingExpressions for evaluation + * + * @author bratseth + */ +public class RankProfilesConfigImporter { + + private static final Pattern expressionPattern = Pattern.compile("rankingExpression\\(([a-zA-Z0-9_]+)\\)\\.rankingScript"); + + /** + * Returns a map of the models contained in this config, indexed on name. + * The map is modifiable and owned by the caller. + */ + public Map importFrom(RankProfilesConfig config) { + Map models = new HashMap<>(); + for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { + Model model = importProfile(profile); + models.put(model.name(), model); + } + return models; + } + + private Model importProfile(RankProfilesConfig.Rankprofile profile) { + System.out.println("Importing " + profile.name()); + List functions = new ArrayList<>(); + for (RankProfilesConfig.Rankprofile.Fef.Property property : profile.fef().property()) { + Matcher expressionMatcher = expressionPattern.matcher(property.name()); + if ( ! expressionMatcher.matches()) continue; + + System.out.println(" Importing " + expressionMatcher.group(0)); + + String name = expressionMatcher.group(0); + List arguments = new ArrayList<>(); + RankingExpression expression = RankingExpression.from(property.value()); + functions.add(new ExpressionFunction(name, arguments, expression)); + } + return new Model(profile.name(), functions); + } + +} diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankprofilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankprofilesConfigImporter.java deleted file mode 100644 index 70964529f53..00000000000 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankprofilesConfigImporter.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.vespa.models.evaluation.config; - -import com.yahoo.vespa.config.search.RankProfilesConfig; - -/** - * Converts RankprofilesConfig instances to RankingExpressions for evaluation - */ -public class RankprofilesConfigImporter { - - public void importFrom(RankProfilesConfig config) { - for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { - - } - } - -} diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java new file mode 100644 index 00000000000..68c7b2dc3cf --- /dev/null +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java @@ -0,0 +1,13 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import org.junit.Test; + +public class ModelsEvaluatorTest { + + @Test + public void testEvaluation() { + + } + +} diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/config/RankProfilesImporterTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/config/RankProfilesImporterTest.java new file mode 100644 index 00000000000..2e93a1a138d --- /dev/null +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/config/RankProfilesImporterTest.java @@ -0,0 +1,51 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation.config; + +import ai.vespa.models.evaluation.Model; +import com.yahoo.config.subscription.ConfigGetter; +import com.yahoo.config.subscription.FileSource; +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.vespa.config.search.RankProfilesConfig; +import org.junit.Test; + +import java.io.File; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests instantiating models from rank-profiles configs. + * + * @author bratseth + */ +public class RankProfilesImporterTest { + + @Test + public void testRankexpression() { + String configPath = "src/test/resources/config/rankexpression/rank-profiles.cfg"; + RankProfilesConfig config = new ConfigGetter<>(new FileSource(new File(configPath)), RankProfilesConfig.class).getConfig(""); + Map models = new RankProfilesConfigImporter().importFrom(config); + assertEquals(18, models.size()); + Model macros = models.get("macros"); + assertNotNull(macros); + assertEquals(4, macros.functions().size()); + ExpressionFunction function = functionByName("fourtimessum", macros); + assertNotNull(function); + + } + + @Test + public void testRegexp() { + assertTrue("a(foo)".matches("a\\([a-zA-Z0-9_]+\\)")); + } + + private ExpressionFunction functionByName(String name, Model model) { + for (ExpressionFunction function : model.functions()) + if (function.getName().equals(name)) + return function; + return null; + } + +} diff --git a/model-inference/src/test/resources/config/rankexpression/rank-profiles.cfg b/model-inference/src/test/resources/config/rankexpression/rank-profiles.cfg new file mode 100644 index 00000000000..f5652c31d2a --- /dev/null +++ b/model-inference/src/test/resources/config/rankexpression/rank-profiles.cfg @@ -0,0 +1,296 @@ +rankprofile[0].name "default" +rankprofile[0].fef.property[0].name "foo" +rankprofile[0].fef.property[0].value "bar, baz" +rankprofile[0].fef.property[1].name "foo" +rankprofile[0].fef.property[1].value "foobar" +rankprofile[0].fef.property[2].name "qux" +rankprofile[0].fef.property[2].value "quux" +rankprofile[0].fef.property[3].name "foo.bar" +rankprofile[0].fef.property[3].value "foo.bar" +rankprofile[0].fef.property[4].name "foo.bar.baz" +rankprofile[0].fef.property[4].value "123" +rankprofile[0].fef.property[5].name "foo(bar).baz.2" +rankprofile[0].fef.property[5].value "123.4" +rankprofile[0].fef.property[6].name "foo(bar).baz.qux" +rankprofile[0].fef.property[6].value "foo(bar)" +rankprofile[0].fef.property[7].name "nud" +rankprofile[0].fef.property[7].value "ity" +rankprofile[0].fef.property[8].name "vespa.rank.firstphase" +rankprofile[0].fef.property[8].value "classicRank" +rankprofile[0].fef.property[9].name "vespa.rank.secondphase" +rankprofile[0].fef.property[9].value "rankingExpression(secondphase)" +rankprofile[0].fef.property[10].name "rankingExpression(secondphase).rankingScript" +rankprofile[0].fef.property[10].value "4" +rankprofile[0].fef.property[11].name "vespa.dump.feature" +rankprofile[0].fef.property[11].value "attribute(foo1).out" +rankprofile[0].fef.property[12].name "vespa.dump.feature" +rankprofile[0].fef.property[12].value "attribute(bar1)" +rankprofile[0].fef.property[13].name "vespa.dump.feature" +rankprofile[0].fef.property[13].value "attribute(foo2).out" +rankprofile[0].fef.property[14].name "vespa.dump.feature" +rankprofile[0].fef.property[14].value "attribute(bar2).out" +rankprofile[0].fef.property[15].name "vespa.dump.feature" +rankprofile[0].fef.property[15].value "attribute(foo3).out" +rankprofile[0].fef.property[16].name "vespa.dump.feature" +rankprofile[0].fef.property[16].value "attribute(bar3).out" +rankprofile[0].fef.property[17].name "vespa.dump.feature" +rankprofile[0].fef.property[17].value "attribute(foo4).out" +rankprofile[0].fef.property[18].name "vespa.dump.feature" +rankprofile[0].fef.property[18].value "attribute(bar4).out" +rankprofile[0].fef.property[19].name "vespa.hitcollector.heapsize" +rankprofile[0].fef.property[19].value "10" +rankprofile[0].fef.property[20].name "vespa.hitcollector.arraysize" +rankprofile[0].fef.property[20].value "20" +rankprofile[0].fef.property[21].name "vespa.hitcollector.rankscoredroplimit" +rankprofile[0].fef.property[21].value "-0.5" +rankprofile[0].fef.property[22].name "vespa.dump.ignoredefaultfeatures" +rankprofile[0].fef.property[22].value "true" +rankprofile[1].name "unranked" +rankprofile[1].fef.property[0].name "vespa.rank.firstphase" +rankprofile[1].fef.property[0].value "value(0)" +rankprofile[1].fef.property[1].name "vespa.hitcollector.heapsize" +rankprofile[1].fef.property[1].value "0" +rankprofile[1].fef.property[2].name "vespa.hitcollector.arraysize" +rankprofile[1].fef.property[2].value "0" +rankprofile[1].fef.property[3].name "vespa.dump.ignoredefaultfeatures" +rankprofile[1].fef.property[3].value "true" +rankprofile[2].name "static" +rankprofile[2].fef.property[0].name "vespa.rank.firstphase" +rankprofile[2].fef.property[0].value "attribute" +rankprofile[2].fef.property[1].name "vespa.rank.secondphase" +rankprofile[2].fef.property[1].value "rankingExpression(secondphase)" +rankprofile[2].fef.property[2].name "rankingExpression(secondphase).rankingScript" +rankprofile[2].fef.property[2].value "10 + feature(arg1).out.out" +rankprofile[2].fef.property[3].name "vespa.summary.feature" +rankprofile[2].fef.property[3].value "attribute(foo1).out" +rankprofile[2].fef.property[4].name "vespa.summary.feature" +rankprofile[2].fef.property[4].value "attribute(bar1)" +rankprofile[2].fef.property[5].name "vespa.summary.feature" +rankprofile[2].fef.property[5].value "attribute(foo2).out" +rankprofile[2].fef.property[6].name "vespa.summary.feature" +rankprofile[2].fef.property[6].value "attribute(bar2).out" +rankprofile[2].fef.property[7].name "vespa.summary.feature" +rankprofile[2].fef.property[7].value "attribute(foo3).out" +rankprofile[2].fef.property[8].name "vespa.summary.feature" +rankprofile[2].fef.property[8].value "attribute(bar3).out" +rankprofile[2].fef.property[9].name "vespa.summary.feature" +rankprofile[2].fef.property[9].value "attribute(foo4).out" +rankprofile[2].fef.property[10].name "vespa.summary.feature" +rankprofile[2].fef.property[10].value "attribute(bar4).out" +rankprofile[3].name "overflow" +rankprofile[3].fef.property[0].name "vespa.rank.firstphase" +rankprofile[3].fef.property[0].value "rankingExpression(firstphase)" +rankprofile[3].fef.property[1].name "rankingExpression(firstphase).rankingScript" +rankprofile[3].fef.property[1].value "feature1(argument1,argument2,argument3,argument4).output + feature2(argument1,argument2,argument3,argument4).output + feature3(argument1,argument2,argument3,argument4).output + feature4(argument1,argument2,argument3,argument4).output + feature5(argument1,argument2,argument3,argument4).output + feature6(argument1,argument2,argument3,argument4).output + feature7(argument1,argument2,argument3,argument4).output + feature8(argument1,argument2,argument3,argument4).output + feature9(argument1,argument2,argument3,argument4).output + feature10(argument1,argument2,argument3,argument4).output + feature11(argument1,argument2,argument3,argument4).output + feature12(argument1,argument2,argument3,argument4).output + feature13(argument1,argument2,argument3,argument4).output + feature14(argument1,argument2,argument3,argument4).output + feature15(argument1,argument2,argument3,argument4).output + feature16(argument1,argument2,argument3,argument4).output + feature17(argument1,argument2,argument3,argument4).output + feature18(argument1,argument2,argument3,argument4).output + feature19(argument1,argument2,argument3,argument4).output + feature20(argument1,argument2,argument3,argument4).output + feature21(argument1,argument2,argument3,argument4).output + feature22(argument1,argument2,argument3,argument4).output + feature23(argument1,argument2,argument3,argument4).output + feature24(argument1,argument2,argument3,argument4).output + feature25(argument1,argument2,argument3,argument4).output + feature26(argument1,argument2,argument3,argument4).output + feature27(argument1,argument2,argument3,argument4).output + feature28(argument1,argument2,argument3,argument4).output + feature29(argument1,argument2,argument3,argument4).output + feature30(argument1,argument2,argument3,argument4).output + feature31(argument1,argument2,argument3,argument4).output + feature32(argument1,argument2,argument3,argument4).output + feature33(argument1,argument2,argument3,argument4).output + feature34(argument1,argument2,argument3,argument4).output + feature35(argument1,argument2,argument3,argument4).output + feature36(argument1,argument2,argument3,argument4).output + feature37(argument1,argument2,argument3,argument4).output + feature38(argument1,argument2,argument3,argument4).output + feature39(argument1,argument2,argument3,argument4).output + feature40(argument1,argument2,argument3,argument4).output + feature41(argument1,argument2,argument3,argument4).output + feature42(argument1,argument2,argument3,argument4).output + feature43(argument1,argument2,argument3,argument4).output + feature44(argument1,argument2,argument3,argument4).output + feature45(argument1,argument2,argument3,argument4).output + feature46(argument1,argument2,argument3,argument4).output + feature47(argument1,argument2,argument3,argument4).output + feature48(argument1,argument2,argument3,argument4).output + feature49(argument1,argument2,argument3,argument4).output + feature50(argument1,argument2,argument3,argument4).output + feature51(argument1,argument2,argument3,argument4).output + feature52(argument1,argument2,argument3,argument4).output + feature53(argument1,argument2,argument3,argument4).output + feature54(argument1,argument2,argument3,argument4).output + feature55(argument1,argument2,argument3,argument4).output + feature56(argument1,argument2,argument3,argument4).output + feature57(argument1,argument2,argument3,argument4).output + feature58(argument1,argument2,argument3,argument4).output + feature59(argument1,argument2,argument3,argument4).output + feature60(argument1,argument2,argument3,argument4).output + feature61(argument1,argument2,argument3,argument4).output + feature62(argument1,argument2,argument3,argument4).output + feature63(argument1,argument2,argument3,argument4).output + feature64(argument1,argument2,argument3,argument4).output + feature65(argument1,argument2,argument3,argument4).output + feature66(argument1,argument2,argument3,argument4).output + feature67(argument1,argument2,argument3,argument4).output + feature68(argument1,argument2,argument3,argument4).output + feature69(argument1,argument2,argument3,argument4).output + feature70(argument1,argument2,argument3,argument4).output + feature71(argument1,argument2,argument3,argument4).output + feature72(argument1,argument2,argument3,argument4).output + feature73(argument1,argument2,argument3,argument4).output + feature74(argument1,argument2,argument3,argument4).output + feature75(argument1,argument2,argument3,argument4).output + feature76(argument1,argument2,argument3,argument4).output + feature77(argument1,argument2,argument3,argument4).output + feature78(argument1,argument2,argument3,argument4).output + feature79(argument1,argument2,argument3,argument4).output + feature80(argument1,argument2,argument3,argument4).output + feature81(argument1,argument2,argument3,argument4).output + feature82(argument1,argument2,argument3,argument4).output + feature83(argument1,argument2,argument3,argument4).output + feature84(argument1,argument2,argument3,argument4).output + feature85(argument1,argument2,argument3,argument4).output + feature86(argument1,argument2,argument3,argument4).output + feature87(argument1,argument2,argument3,argument4).output + feature88(argument1,argument2,argument3,argument4).output + feature89(argument1,argument2,argument3,argument4).output + feature90(argument1,argument2,argument3,argument4).output + feature91(argument1,argument2,argument3,argument4).output + feature92(argument1,argument2,argument3,argument4).output + feature93(argument1,argument2,argument3,argument4).output + feature94(argument1,argument2,argument3,argument4).output + feature95(argument1,argument2,argument3,argument4).output + feature96(argument1,argument2,argument3,argument4).output + feature97(argument1,argument2,argument3,argument4).output + feature98(argument1,argument2,argument3,argument4).output + feature99(argument1,argument2,argument3,argument4).output + feature100(argument1,argument2,argument3,argument4).output + feature101(argument1,argument2,argument3,argument4).output + feature102(argument1,argument2,argument3,argument4).output + feature103(argument1,argument2,argument3,argument4).output + feature104(argument1,argument2,argument3,argument4).output + feature105(argument1,argument2,argument3,argument4).output + feature106(argument1,argument2,argument3,argument4).output + feature107(argument1,argument2,argument3,argument4).output + feature108(argument1,argument2,argument3,argument4).output + feature109(argument1,argument2,argument3,argument4).output + feature110(argument1,argument2,argument3,argument4).output + feature111(argument1,argument2,argument3,argument4).output + feature112(argument1,argument2,argument3,argument4).output + feature113(argument1,argument2,argument3,argument4).output + feature114(argument1,argument2,argument3,argument4).output + feature115(argument1,argument2,argument3,argument4).output + feature116(argument1,argument2,argument3,argument4).output + feature117(argument1,argument2,argument3,argument4).output + feature118(argument1,argument2,argument3,argument4).output + feature119(argument1,argument2,argument3,argument4).output + feature120(argument1,argument2,argument3,argument4).output + feature121(argument1,argument2,argument3,argument4).output + feature122(argument1,argument2,argument3,argument4).output + feature123(argument1,argument2,argument3,argument4).output + feature124(argument1,argument2,argument3,argument4).output + feature125(argument1,argument2,argument3,argument4).output + feature126(argument1,argument2,argument3,argument4).output + feature127(argument1,argument2,argument3,argument4).output + feature128(argument1,argument2,argument3,argument4).output + feature129(argument1,argument2,argument3,argument4).output + feature130(argument1,argument2,argument3,argument4).output + feature131(argument1,argument2,argument3,argument4).output + feature132(argument1,argument2,argument3,argument4).output + feature133(argument1,argument2,argument3,argument4).output + feature134(argument1,argument2,argument3,argument4).output + feature135(argument1,argument2,argument3,argument4).output + feature136(argument1,argument2,argument3,argument4).output + feature137(argument1,argument2,argument3,argument4).output + feature138(argument1,argument2,argument3,argument4).output + feature139(argument1,argument2,argument3,argument4).output + feature140(argument1,argument2,argument3,argument4).output + feature141(argument1,argument2,argument3,argument4).output + feature142(argument1,argument2,argument3,argument4).output + feature143(argument1,argument2,argument3,argument4).output + feature144(argument1,argument2,argument3,argument4).output + feature145(argument1,argument2,argument3,argument4).output + feature146(argument1,argument2,argument3,argument4).output + feature147(argument1,argument2,argument3,argument4).output + feature148(argument1,argument2,argument3,argument4).output + feature149(argument1,argument2,argument3,argument4).output + feature150(argument1,argument2,argument3,argument4).output + feature151(argument1,argument2,argument3,argument4).output + feature152(argument1,argument2,argument3,argument4).output + feature153(argument1,argument2,argument3,argument4).output + feature154(argument1,argument2,argument3,argument4).output + feature155(argument1,argument2,argument3,argument4).output + feature156(argument1,argument2,argument3,argument4).output + feature157(argument1,argument2,argument3,argument4).output + feature158(argument1,argument2,argument3,argument4).output + feature159(argument1,argument2,argument3,argument4).output + feature160(argument1,argument2,argument3,argument4).output + feature161(argument1,argument2,argument3,argument4).output + feature162(argument1,argument2,argument3,argument4).output + feature163(argument1,argument2,argument3,argument4).output + feature164(argument1,argument2,argument3,argument4).output + feature165(argument1,argument2,argument3,argument4).output + feature166(argument1,argument2,argument3,argument4).output + feature167(argument1,argument2,argument3,argument4).output + feature168(argument1,argument2,argument3,argument4).output + feature169(argument1,argument2,argument3,argument4).output + feature170(argument1,argument2,argument3,argument4).output + feature171(argument1,argument2,argument3,argument4).output + feature172(argument1,argument2,argument3,argument4).output + feature173(argument1,argument2,argument3,argument4).output + feature174(argument1,argument2,argument3,argument4).output + feature175(argument1,argument2,argument3,argument4).output + feature176(argument1,argument2,argument3,argument4).output + feature177(argument1,argument2,argument3,argument4).output + feature178(argument1,argument2,argument3,argument4).output + feature179(argument1,argument2,argument3,argument4).output + feature180(argument1,argument2,argument3,argument4).output + feature181(argument1,argument2,argument3,argument4).output + feature182(argument1,argument2,argument3,argument4).output + feature183(argument1,argument2,argument3,argument4).output + feature184(argument1,argument2,argument3,argument4).output + feature185(argument1,argument2,argument3,argument4).output + feature186(argument1,argument2,argument3,argument4).output + feature187(argument1,argument2,argument3,argument4).output + feature188(argument1,argument2,argument3,argument4).output + feature189(argument1,argument2,argument3,argument4).output + feature190(argument1,argument2,argument3,argument4).output + feature191(argument1,argument2,argument3,argument4).output + feature192(argument1,argument2,argument3,argument4).output + feature193(argument1,argument2,argument3,argument4).output + feature194(argument1,argument2,argument3,argument4).output + feature195(argument1,argument2,argument3,argument4).output + feature196(argument1,argument2,argument3,argument4).output + feature197(argument1,argument2,argument3,argument4).output + feature198(argument1,argument2,argument3,argument4).output + feature199(argument1,argument2,argument3,argument4).output + feature200(argument1,argument2,argument3,argument4).output + feature201(argument1,argument2,argument3,argument4).output + feature202(argument1,argument2,argument3,argument4).output + feature203(argument1,argument2,argument3,argument4).output + feature204(argument1,argument2,argument3,argument4).output + feature205(argument1,argument2,argument3,argument4).output + feature206(argument1,argument2,argument3,argument4).output + feature207(argument1,argument2,argument3,argument4).output + feature208(argument1,argument2,argument3,argument4).output + feature209(argument1,argument2,argument3,argument4).output + feature210(argument1,argument2,argument3,argument4).output + feature211(argument1,argument2,argument3,argument4).output + feature212(argument1,argument2,argument3,argument4).output + feature213(argument1,argument2,argument3,argument4).output + feature214(argument1,argument2,argument3,argument4).output + feature215(argument1,argument2,argument3,argument4).output + feature216(argument1,argument2,argument3,argument4).output + feature217(argument1,argument2,argument3,argument4).output + feature218(argument1,argument2,argument3,argument4).output + feature219(argument1,argument2,argument3,argument4).output + feature220(argument1,argument2,argument3,argument4).output + feature221(argument1,argument2,argument3,argument4).output + feature222(argument1,argument2,argument3,argument4).output + feature223(argument1,argument2,argument3,argument4).output + feature224(argument1,argument2,argument3,argument4).output + feature225(argument1,argument2,argument3,argument4).output + feature226(argument1,argument2,argument3,argument4).output + feature227(argument1,argument2,argument3,argument4).output + feature228(argument1,argument2,argument3,argument4).output + feature229(argument1,argument2,argument3,argument4).output + feature230(argument1,argument2,argument3,argument4).output + feature231(argument1,argument2,argument3,argument4).output + feature232(argument1,argument2,argument3,argument4).output + feature233(argument1,argument2,argument3,argument4).output + feature234(argument1,argument2,argument3,argument4).output + feature235(argument1,argument2,argument3,argument4).output + feature236(argument1,argument2,argument3,argument4).output + feature237(argument1,argument2,argument3,argument4).output + feature238(argument1,argument2,argument3,argument4).output + feature239(argument1,argument2,argument3,argument4).output + feature240(argument1,argument2,argument3,argument4).output + feature241(argument1,argument2,argument3,argument4).output + feature242(argument1,argument2,argument3,argument4).output + feature243(argument1,argument2,argument3,argument4).output + feature244(argument1,argument2,argument3,argument4).output + feature245(argument1,argument2,argument3,argument4).output + feature246(argument1,argument2,argument3,argument4).output + feature247(argument1,argument2,argument3,argument4).output + feature248(argument1,argument2,argument3,argument4).output + feature249(argument1,argument2,argument3,argument4).output + feature250(argument1,argument2,argument3,argument4).output + feature251(argument1,argument2,argument3,argument4).output + feature252(argument1,argument2,argument3,argument4).output + feature253(argument1,argument2,argument3,argument4).output + feature254(argument1,argument2,argument3,argument4).output + feature255(argument1,argument2,argument3,argument4).output + feature256(argument1,argument2,argument3,argument4).output + feature257(argument1,argument2,argument3,argument4).output + feature258(argument1,argument2,argument3,argument4).output + feature259(argument1,argument2,argument3,argument4).output + feature260(argument1,argument2,argument3,argument4).output + feature261(argument1,argument2,argument3,argument4).output + feature262(argument1,argument2,argument3,argument4).output + feature263(argument1,argument2,argument3,argument4).output + feature264(argument1,argument2,argument3,argument4).output + feature265(argument1,argument2,argument3,argument4).output + feature266(argument1,argument2,argument3,argument4).output + feature267(argument1,argument2,argument3,argument4).output + feature268(argument1,argument2,argument3,argument4).output + feature269(argument1,argument2,argument3,argument4).output + feature270(argument1,argument2,argument3,argument4).output + feature271(argument1,argument2,argument3,argument4).output + feature272(argument1,argument2,argument3,argument4).output + feature273(argument1,argument2,argument3,argument4).output + feature274(argument1,argument2,argument3,argument4).output + feature275(argument1,argument2,argument3,argument4).output + feature276(argument1,argument2,argument3,argument4).output + feature277(argument1,argument2,argument3,argument4).output + feature278(argument1,argument2,argument3,argument4).output + feature279(argument1,argument2,argument3,argument4).output + feature280(argument1,argument2,argument3,argument4).output + feature281(argument1,argument2,argument3,argument4).output + feature282(argument1,argument2,argument3,argument4).output + feature283(argument1,argument2,argument3,argument4).output + feature284(argument1,argument2,argument3,argument4).output + feature285(argument1,argument2,argument3,argument4).output + feature286(argument1,argument2,argument3,argument4).output + feature287(argument1,argument2,argument3,argument4).output + feature288(argument1,argument2,argument3,argument4).output + feature289(argument1,argument2,argument3,argument4).output + feature290(argument1,argument2,argument3,argument4).output + feature291(argument1,argument2,argument3,argument4).output + feature292(argument1,argument2,argument3,argument4).output + feature293(argument1,argument2,argument3,argument4).output + feature294(argument1,argument2,argument3,argument4).output + feature295(argument1,argument2,argument3,argument4).output + feature296(argument1,argument2,argument3,argument4).output + feature297(argument1,argument2,argument3,argument4).output + feature298(argument1,argument2,argument3,argument4).output + feature299(argument1,argument2,argument3,argument4).output + feature300(argument1,argument2,argument3,argument4).output" +rankprofile[3].fef.property[2].name "vespa.rank.secondphase" +rankprofile[3].fef.property[2].value "rankingExpression(secondphase)" +rankprofile[3].fef.property[3].name "rankingExpression(secondphase).rankingScript" +rankprofile[3].fef.property[3].value "exp(0) + mysum(attribute(foo),\"attribute( bar )\",\"attribute( \\\"baz\\\" )\")" +rankprofile[3].fef.property[4].name "vespa.hitcollector.heapsize" +rankprofile[3].fef.property[4].value "101" +rankprofile[3].fef.property[5].name "vespa.hitcollector.arraysize" +rankprofile[3].fef.property[5].value "201" +rankprofile[3].fef.property[6].name "vespa.hitcollector.rankscoredroplimit" +rankprofile[3].fef.property[6].value "501.5" +rankprofile[4].name "duplicates" +rankprofile[4].fef.property[0].name "fieldMatch(a).proximityLimit" +rankprofile[4].fef.property[0].value "4" +rankprofile[4].fef.property[1].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[1].value "0.2" +rankprofile[4].fef.property[2].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[2].value "0.4" +rankprofile[4].fef.property[3].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[3].value "0.6" +rankprofile[4].fef.property[4].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[4].value "0.8" +rankprofile[4].fef.property[5].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[5].value "1" +rankprofile[4].fef.property[6].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[6].value "0.8" +rankprofile[4].fef.property[7].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[7].value "0.6" +rankprofile[4].fef.property[8].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[8].value "0.4" +rankprofile[4].fef.property[9].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[9].value "0.2" +rankprofile[5].name "whitespace1" +rankprofile[5].fef.property[0].name "vespa.rank.firstphase" +rankprofile[5].fef.property[0].value "rankingExpression(firstphase)" +rankprofile[5].fef.property[1].name "rankingExpression(firstphase).rankingScript" +rankprofile[5].fef.property[1].value "1" +rankprofile[6].name "whitespace2" +rankprofile[6].fef.property[0].name "vespa.rank.firstphase" +rankprofile[6].fef.property[0].value "rankingExpression(firstphase)" +rankprofile[6].fef.property[1].name "rankingExpression(firstphase).rankingScript" +rankprofile[6].fef.property[1].value "1" +rankprofile[7].name "macros" +rankprofile[7].fef.property[0].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[7].fef.property[0].value "4 * (var1 + var2)" +rankprofile[7].fef.property[1].name "rankingExpression(myfeature).rankingScript" +rankprofile[7].fef.property[1].value "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + 30 * pow(0 - fieldMatch(description).earliness,2)" +rankprofile[7].fef.property[2].name "rankingExpression(fourtimessum@5cf279212355b980.67f1e87166cfef86).rankingScript" +rankprofile[7].fef.property[2].value "4 * (match + rankBoost)" +rankprofile[7].fef.property[3].name "vespa.rank.firstphase" +rankprofile[7].fef.property[3].value "rankingExpression(firstphase)" +rankprofile[7].fef.property[4].name "rankingExpression(firstphase).rankingScript" +rankprofile[7].fef.property[4].value "match + fieldMatch(title) + rankingExpression(myfeature)" +rankprofile[7].fef.property[5].name "vespa.rank.secondphase" +rankprofile[7].fef.property[5].value "rankingExpression(fourtimessum@5cf279212355b980.67f1e87166cfef86)" +rankprofile[7].fef.property[6].name "vespa.summary.feature" +rankprofile[7].fef.property[6].value "fieldMatch(title)" +rankprofile[8].name "macros2" +rankprofile[8].fef.property[0].name "foo" +rankprofile[8].fef.property[0].value "some, list" +rankprofile[8].fef.property[1].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[8].fef.property[1].value "4 * (var1 + var2)" +rankprofile[8].fef.property[2].name "rankingExpression(myfeature).rankingScript" +rankprofile[8].fef.property[2].value "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + 30 * pow(0 - fieldMatch(description).earliness,2)" +rankprofile[8].fef.property[3].name "rankingExpression(mysummaryfeature).rankingScript" +rankprofile[8].fef.property[3].value "70 * fieldMatch(title).completeness" +rankprofile[8].fef.property[4].name "rankingExpression(mysummaryfeature2).rankingScript" +rankprofile[8].fef.property[4].value "71 * fieldMatch(title).completeness" +rankprofile[8].fef.property[5].name "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86).rankingScript" +rankprofile[8].fef.property[5].value "4 * (match + match)" +rankprofile[8].fef.property[6].name "vespa.rank.firstphase" +rankprofile[8].fef.property[6].value "classicRank" +rankprofile[8].fef.property[7].name "vespa.rank.secondphase" +rankprofile[8].fef.property[7].value "rankingExpression(secondphase)" +rankprofile[8].fef.property[8].name "rankingExpression(secondphase).rankingScript" +rankprofile[8].fef.property[8].value "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86) + rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[8].fef.property[9].name "vespa.summary.feature" +rankprofile[8].fef.property[9].value "rankingExpression(mysummaryfeature2)" +rankprofile[8].fef.property[10].name "vespa.summary.feature" +rankprofile[8].fef.property[10].value "rankingExpression(mysummaryfeature)" +rankprofile[9].name "macros3" +rankprofile[9].fef.property[0].name "rankingExpression(onlyusedinsummaryfeature).rankingScript" +rankprofile[9].fef.property[0].value "5" +rankprofile[9].fef.property[1].name "vespa.summary.feature" +rankprofile[9].fef.property[1].value "rankingExpression(matches(title,rankingExpression(onlyusedinsummaryfeature)))" +rankprofile[10].name "macros3-inherited" +rankprofile[10].fef.property[0].name "rankingExpression(onlyusedinsummaryfeature).rankingScript" +rankprofile[10].fef.property[0].value "5" +rankprofile[10].fef.property[1].name "vespa.summary.feature" +rankprofile[10].fef.property[1].value "rankingExpression(matches(title,rankingExpression(onlyusedinsummaryfeature)))" +rankprofile[11].name "macros-inherited" +rankprofile[11].fef.property[0].name "foo" +rankprofile[11].fef.property[0].value "some, list" +rankprofile[11].fef.property[1].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[11].fef.property[1].value "4 * (var1 + var2)" +rankprofile[11].fef.property[2].name "rankingExpression(myfeature).rankingScript" +rankprofile[11].fef.property[2].value "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + 30 * pow(0 - fieldMatch(description).earliness,2)" +rankprofile[11].fef.property[3].name "rankingExpression(mysummaryfeature).rankingScript" +rankprofile[11].fef.property[3].value "80 * fieldMatch(title).completeness" +rankprofile[11].fef.property[4].name "rankingExpression(mysummaryfeature2).rankingScript" +rankprofile[11].fef.property[4].value "71 * fieldMatch(title).completeness" +rankprofile[11].fef.property[5].name "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86).rankingScript" +rankprofile[11].fef.property[5].value "4 * (match + match)" +rankprofile[11].fef.property[6].name "vespa.rank.firstphase" +rankprofile[11].fef.property[6].value "rankingExpression(firstphase)" +rankprofile[11].fef.property[7].name "rankingExpression(firstphase).rankingScript" +rankprofile[11].fef.property[7].value "20000 * rankingExpression(myfeature) + rankingExpression(mysummaryfeature)" +rankprofile[11].fef.property[8].name "vespa.rank.secondphase" +rankprofile[11].fef.property[8].value "rankingExpression(secondphase)" +rankprofile[11].fef.property[9].name "rankingExpression(secondphase).rankingScript" +rankprofile[11].fef.property[9].value "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86) + rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[11].fef.property[10].name "vespa.summary.feature" +rankprofile[11].fef.property[10].value "rankingExpression(mysummaryfeature2)" +rankprofile[11].fef.property[11].name "vespa.summary.feature" +rankprofile[11].fef.property[11].value "rankingExpression(mysummaryfeature)" +rankprofile[12].name "macros-inherited2" +rankprofile[12].fef.property[0].name "foo" +rankprofile[12].fef.property[0].value "some, list" +rankprofile[12].fef.property[1].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[12].fef.property[1].value "4 * (var1 + var2)" +rankprofile[12].fef.property[2].name "rankingExpression(myfeature).rankingScript" +rankprofile[12].fef.property[2].value "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + 30 * pow(0 - fieldMatch(description).earliness,2)" +rankprofile[12].fef.property[3].name "rankingExpression(mysummaryfeature).rankingScript" +rankprofile[12].fef.property[3].value "80 * fieldMatch(title).completeness" +rankprofile[12].fef.property[4].name "rankingExpression(mysummaryfeature2).rankingScript" +rankprofile[12].fef.property[4].value "71 * fieldMatch(title).completeness" +rankprofile[12].fef.property[5].name "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86).rankingScript" +rankprofile[12].fef.property[5].value "4 * (match + match)" +rankprofile[12].fef.property[6].name "vespa.rank.firstphase" +rankprofile[12].fef.property[6].value "rankingExpression(firstphase)" +rankprofile[12].fef.property[7].name "rankingExpression(firstphase).rankingScript" +rankprofile[12].fef.property[7].value "30000 * rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[12].fef.property[8].name "vespa.rank.secondphase" +rankprofile[12].fef.property[8].value "rankingExpression(secondphase)" +rankprofile[12].fef.property[9].name "rankingExpression(secondphase).rankingScript" +rankprofile[12].fef.property[9].value "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86) + rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[12].fef.property[10].name "vespa.summary.feature" +rankprofile[12].fef.property[10].value "rankingExpression(mysummaryfeature2)" +rankprofile[12].fef.property[11].name "vespa.summary.feature" +rankprofile[12].fef.property[11].value "rankingExpression(mysummaryfeature)" +rankprofile[13].name "macros-inherited3" +rankprofile[13].fef.property[0].name "foo" +rankprofile[13].fef.property[0].value "some, list" +rankprofile[13].fef.property[1].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[13].fef.property[1].value "4 * (var1 + var2)" +rankprofile[13].fef.property[2].name "rankingExpression(myfeature).rankingScript" +rankprofile[13].fef.property[2].value "700 * fieldMatch(title).completeness" +rankprofile[13].fef.property[3].name "rankingExpression(mysummaryfeature).rankingScript" +rankprofile[13].fef.property[3].value "80 * fieldMatch(title).completeness" +rankprofile[13].fef.property[4].name "rankingExpression(mysummaryfeature2).rankingScript" +rankprofile[13].fef.property[4].value "71 * fieldMatch(title).completeness" +rankprofile[13].fef.property[5].name "vespa.rank.firstphase" +rankprofile[13].fef.property[5].value "rankingExpression(firstphase)" +rankprofile[13].fef.property[6].name "rankingExpression(firstphase).rankingScript" +rankprofile[13].fef.property[6].value "30000 * rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[13].fef.property[7].name "vespa.rank.secondphase" +rankprofile[13].fef.property[7].value "rankingExpression(secondphase)" +rankprofile[13].fef.property[8].name "rankingExpression(secondphase).rankingScript" +rankprofile[13].fef.property[8].value "40000 * rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[13].fef.property[9].name "vespa.summary.feature" +rankprofile[13].fef.property[9].value "rankingExpression(mysummaryfeature2)" +rankprofile[13].fef.property[10].name "vespa.summary.feature" +rankprofile[13].fef.property[10].value "rankingExpression(mysummaryfeature)" +rankprofile[14].name "macros-refering-macros" +rankprofile[14].fef.property[0].name "rankingExpression(m1).rankingScript" +rankprofile[14].fef.property[0].value "700 * fieldMatch(title).completeness" +rankprofile[14].fef.property[1].name "rankingExpression(m2).rankingScript" +rankprofile[14].fef.property[1].value "rankingExpression(m1) * 67" +rankprofile[14].fef.property[2].name "rankingExpression(m4).rankingScript" +rankprofile[14].fef.property[2].value "703 * fieldMatch(fromfile).completeness" +rankprofile[14].fef.property[3].name "vespa.rank.secondphase" +rankprofile[14].fef.property[3].value "rankingExpression(secondphase)" +rankprofile[14].fef.property[4].name "rankingExpression(secondphase).rankingScript" +rankprofile[14].fef.property[4].value "40000 * rankingExpression(m2)" +rankprofile[15].name "macros-refering-macros-inherited" +rankprofile[15].fef.property[0].name "rankingExpression(m1).rankingScript" +rankprofile[15].fef.property[0].value "700 * fieldMatch(title).completeness" +rankprofile[15].fef.property[1].name "rankingExpression(m2).rankingScript" +rankprofile[15].fef.property[1].value "rankingExpression(m1) * 67" +rankprofile[15].fef.property[2].name "rankingExpression(m4).rankingScript" +rankprofile[15].fef.property[2].value "701 * fieldMatch(title).completeness" +rankprofile[15].fef.property[3].name "rankingExpression(m3).rankingScript" +rankprofile[15].fef.property[3].value "if (isNan(attribute(nrtgmp)) == 1, 0.0, rankingExpression(m2))" +rankprofile[15].fef.property[4].name "vespa.rank.secondphase" +rankprofile[15].fef.property[4].value "rankingExpression(secondphase)" +rankprofile[15].fef.property[5].name "rankingExpression(secondphase).rankingScript" +rankprofile[15].fef.property[5].value "3000 * rankingExpression(m2)" +rankprofile[16].name "macros-refering-macros-inherited2" +rankprofile[16].fef.property[0].name "rankingExpression(m1).rankingScript" +rankprofile[16].fef.property[0].value "700 * fieldMatch(title).completeness" +rankprofile[16].fef.property[1].name "rankingExpression(m2).rankingScript" +rankprofile[16].fef.property[1].value "rankingExpression(m1) * 67" +rankprofile[16].fef.property[2].name "rankingExpression(m4).rankingScript" +rankprofile[16].fef.property[2].value "703 * fieldMatch(fromfile).completeness" +rankprofile[16].fef.property[3].name "vespa.rank.secondphase" +rankprofile[16].fef.property[3].value "rankingExpression(secondphase)" +rankprofile[16].fef.property[4].name "rankingExpression(secondphase).rankingScript" +rankprofile[16].fef.property[4].value "3002 * rankingExpression(m2)" +rankprofile[17].name "macros-refering-macros-inherited-two-levels" +rankprofile[17].fef.property[0].name "rankingExpression(m1).rankingScript" +rankprofile[17].fef.property[0].value "700 * fieldMatch(title).completeness" +rankprofile[17].fef.property[1].name "rankingExpression(m2).rankingScript" +rankprofile[17].fef.property[1].value "rankingExpression(m1) * 67" +rankprofile[17].fef.property[2].name "rankingExpression(m4).rankingScript" +rankprofile[17].fef.property[2].value "701 * fieldMatch(title).completeness" +rankprofile[17].fef.property[3].name "rankingExpression(m3).rankingScript" +rankprofile[17].fef.property[3].value "if (isNan(attribute(nrtgmp)) == 1, 0.0, rankingExpression(m2))" +rankprofile[17].fef.property[4].name "rankingExpression(m5).rankingScript" +rankprofile[17].fef.property[4].value "if (isNan(attribute(glmpfw)) == 1, rankingExpression(m1), rankingExpression(m4))" +rankprofile[17].fef.property[5].name "vespa.rank.secondphase" +rankprofile[17].fef.property[5].value "rankingExpression(secondphase)" +rankprofile[17].fef.property[6].name "rankingExpression(secondphase).rankingScript" +rankprofile[17].fef.property[6].value "3000 * rankingExpression(m2)" \ No newline at end of file diff --git a/model-inference/src/test/resources/config/rankexpression/rankexpression.sd b/model-inference/src/test/resources/config/rankexpression/rankexpression.sd new file mode 100644 index 00000000000..d3e0057cfe1 --- /dev/null +++ b/model-inference/src/test/resources/config/rankexpression/rankexpression.sd @@ -0,0 +1,327 @@ +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +search rankexpression { + + document rankexpression { + + field artist type string { + indexing: summary | index + } + + field title type string { + indexing: summary | index + } + + field surl type string { + indexing: summary + } + + field year type int { + indexing: summary | attribute + } + + field foo1 type int { + indexing: attribute + } + + field foo2 type int { + indexing: attribute + } + + field foo3 type int { + indexing: attribute + } + + field foo4 type int { + indexing: attribute + } + + field bar1 type int { + indexing: attribute + } + + field bar2 type int { + indexing: attribute + } + + field bar3 type int { + indexing: attribute + } + + field bar4 type int { + indexing: attribute + } + + } + + rank-profile default { + first-phase { + expression: classicRank + keep-rank-count: 20 + rank-score-drop-limit: -0.5 + } + second-phase { + expression: if(3>2,4,2) + rerank-count: 10 + } + rank-features: attribute(foo1).out attribute(bar1) + rank-features { attribute(foo2).out attribute(bar2).out } + rank-features { + attribute(foo3).out attribute(bar3).out } + rank-features { + attribute(foo4).out + attribute(bar4).out + } + ignore-default-rank-features + + rank-properties { + foo: "bar, baz" + qux: "quux" + foo: "foobar" + foo.bar: "foo.bar" + foo.bar.baz: 123 + foo ( bar ) . baz.2 : 123.4 + foo(bar).baz.qux: "foo(bar)" + "nud":"ity" + } + + } + + rank-profile static { + first-phase { + expression { attribute } + } + second-phase { + expression { + file:rankexpression + } + } + summary-features: attribute(foo1).out attribute(bar1) + summary-features { attribute(foo2).out attribute(bar2).out } + summary-features { + attribute(foo3).out attribute(bar3).out } + summary-features { + attribute(foo4).out + attribute(bar4).out + } + } + + rank-profile overflow { + first-phase { + expression: file:overflow.expression + keep-rank-count: 201 + rank-score-drop-limit: 501.5 + } + second-phase { + expression { + exp(0) + + mysum(attribute(foo), + "attribute( bar )", + "attribute( \"baz\" )") + } + rerank-count: 101 + } + } + + rank-profile duplicates { + rank-properties { + fieldMatch(a).proximityLimit: 4 + fieldMatch(a).proximityTable: 0.2 + fieldMatch(a).proximityTable: 0.4 + fieldMatch(a).proximityTable: 0.6 + fieldMatch(a).proximityTable: 0.8 + fieldMatch(a).proximityTable: 1 + fieldMatch(a).proximityTable: 0.8 + fieldMatch(a).proximityTable: 0.6 + fieldMatch(a).proximityTable: 0.4 + fieldMatch(a).proximityTable: 0.2 + } + } + + rank-profile whitespace1 { + first-phase { + expression + { + + 1 + }}} + + rank-profile whitespace2 { + first-phase + { + expression { 1 } + } + } + + rank-profile macros { + first-phase { + expression: match + fieldMatch(title) + myfeature + } + second-phase { + expression: fourtimessum(match,rankBoost) + } + macro fourtimessum(var1, var2) { + expression: 4*(var1+var2) + } + macro myfeature() { + expression { + 70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness, 2) + + 30 * pow(0 - fieldMatch(description).earliness, 2) + } + } + summary-features { + fieldMatch(title) + } + } + + rank-profile macros2 { + first-phase { + expression: classicRank + } + rank-properties { + foo: "some, list" + } + + second-phase { + expression: fourtimessum(match,match) + mysummaryfeature + myfeature + } + macro fourtimessum(var1, var2) { + expression: 4*(var1+var2) + } + macro myfeature() { + expression { + 70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness, 2) + + 30 * pow(0 - fieldMatch(description).earliness, 2) + } + } + macro mysummaryfeature() { + expression { + 70 * fieldMatch(title).completeness + } + } + macro mysummaryfeature2() { + expression { + 71 * fieldMatch(title).completeness + } + } + summary-features { + mysummaryfeature + rankingExpression(mysummaryfeature2) # Required form earlier + } + } + + rank-profile macros3 { + macro onlyusedinsummaryfeature() { + expression: 5 + } + summary-features { + rankingExpression(matches(title,rankingExpression(onlyusedinsummaryfeature))) + } + + } + + rank-profile macros3-inherited inherits macros3 { + summary-features { + rankingExpression(matches(title,rankingExpression(onlyusedinsummaryfeature))) + } + } + + rank-profile macros-inherited inherits macros2 { + macro mysummaryfeature() { + expression { + 80 * fieldMatch(title).completeness + } + } + first-phase { + expression { + 20000 * myfeature + mysummaryfeature + } + } + } + + rank-profile macros-inherited2 inherits macros-inherited { + first-phase { + expression { + 30000 * mysummaryfeature + myfeature + } + } + } + + rank-profile macros-inherited3 inherits macros-inherited2 { + macro myfeature() { + expression { + 700 * fieldMatch(title).completeness + } + } + second-phase { + expression { + 40000 * mysummaryfeature + myfeature + } + } + } + + rank-profile macros-refering-macros { + macro m2() { + expression: m1 * 67 + } + + macro m1() { + expression { + 700 * fieldMatch(title).completeness + } + } + + macro m4() { + expression: file:macro.expression + } + + second-phase { + expression { + 40000 * m2 + } + } + + } + + rank-profile macros-refering-macros-inherited inherits macros-refering-macros { + macro m3() { + expression { + if(isNan(attribute(nrtgmp))==1, + 0.0, + (m2) + ) + } + } + macro m4() { + expression { + 701 * fieldMatch(title).completeness + } + } + second-phase { + expression { + 3000 * m2 + } + } + } + + rank-profile macros-refering-macros-inherited2 inherits macros-refering-macros { + second-phase { + expression { + 3002 * m2 + } + } + } + + rank-profile macros-refering-macros-inherited-two-levels inherits macros-refering-macros-inherited { + macro m5() { + expression { + if(isNan(attribute(glmpfw))==1, + m1, + (m4) + ) + } + } + } + +} + + -- cgit v1.2.3 From 99228d5d9bfa7aea7472895e9c5c959d9b5fba1b Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Fri, 6 Jul 2018 13:17:25 +0200 Subject: Import bound functions separately --- .../com/yahoo/searchdefinition/RankProfile.java | 5 +- .../java/ai/vespa/models/evaluation/Model.java | 23 +++++- .../vespa/models/evaluation/ModelsEvaluator.java | 1 - .../evaluation/RankProfilesConfigImporter.java | 82 ++++++++++++++++++++++ .../config/RankProfilesConfigImporter.java | 55 --------------- .../evaluation/RankProfilesImporterTest.java | 48 +++++++++++++ .../config/RankProfilesImporterTest.java | 51 -------------- 7 files changed, 155 insertions(+), 110 deletions(-) create mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java delete mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/config/RankProfilesConfigImporter.java create mode 100644 model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java delete mode 100644 model-inference/src/test/java/ai/vespa/models/evaluation/config/RankProfilesImporterTest.java diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java index a69c524e863..5855dedc415 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java @@ -932,8 +932,8 @@ public class RankProfile implements Serializable, Cloneable { public static class Macro implements Serializable, Cloneable { private final String name; - private String textualExpression=null; - private RankingExpression expression=null; + private String textualExpression = null; + private RankingExpression expression = null; private List formalParams = new ArrayList<>(); /** True if this should be inlined into calling expressions. Useful for very cheap macros. */ @@ -998,6 +998,7 @@ public class RankProfile implements Serializable, Cloneable { } public static final class DiversitySettings { + private String attribute = null; private int minGroups = 0; private double cutoffFactor = 10; diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java index 76d990a493e..b2eb8546a7c 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; import java.util.Collection; +import java.util.Collections; import java.util.List; /** @@ -16,18 +17,38 @@ public class Model { private final String name; + /** Free functions */ private final ImmutableList functions; + /** An instance of each usage of the above function, where variables are replaced by their bindings */ + private final ImmutableList boundFunctions; + public Model(String name, Collection functions) { + this(name, functions, Collections.emptyList()); + } + + Model(String name, Collection functions, Collection boundFunctions) { this.name = name; this.functions = ImmutableList.copyOf(functions); + this.boundFunctions = ImmutableList.copyOf(boundFunctions); } public String name() { return name; } - /** Returns an immutable list of the expression functions of this */ + /** Returns an immutable list of the free (callable) functions of this */ public List functions() { return functions; } + /** Returns an immutable list of the bound function instances of this */ + List boundFunctions() { return functions; } + + /** Returns the function withe the given name, or null if none */ // TODO: Parameter overloading? + ExpressionFunction function(String name) { + for (ExpressionFunction function : functions) + if (function.getName().equals(name)) + return function; + return null; + } + @Override public String toString() { return "Model '" + name + "'"; } diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java index c1a22a361e9..3532409766b 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java @@ -1,7 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.models.evaluation; -import ai.vespa.models.evaluation.config.RankProfilesConfigImporter; import com.yahoo.vespa.config.search.RankProfilesConfig; /** diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java new file mode 100644 index 00000000000..30eabb6f8a4 --- /dev/null +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java @@ -0,0 +1,82 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import ai.vespa.models.evaluation.Model; +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.RankingExpression; +import com.yahoo.vespa.config.search.RankProfilesConfig; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Converts RankProfilesConfig instances to RankingExpressions for evaluation + * + * @author bratseth + */ +class RankProfilesConfigImporter { + + private static final Pattern expressionPattern = + Pattern.compile("rankingExpression\\(([a-zA-Z0-9_]+)(@[a-f0-9]+\\.[a-f0-9]+)?\\)\\.rankingScript"); + + /** + * Returns a map of the models contained in this config, indexed on name. + * The map is modifiable and owned by the caller. + */ + public Map importFrom(RankProfilesConfig config) { + Map models = new HashMap<>(); + for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { + Model model = importProfile(profile); + models.put(model.name(), model); + } + return models; + } + + private Model importProfile(RankProfilesConfig.Rankprofile profile) { + System.out.println("Importing " + profile.name()); + List functions = new ArrayList<>(); + List boundFunctions = new ArrayList<>(); + ExpressionFunction firstPhase = null; + ExpressionFunction secondPhase = null; + for (RankProfilesConfig.Rankprofile.Fef.Property property : profile.fef().property()) { + Matcher expressionMatcher = expressionPattern.matcher(property.name()); + if ( expressionMatcher.matches()) { + String name = expressionMatcher.group(1); + String instance = expressionMatcher.group(2); + List arguments = new ArrayList<>(); + RankingExpression expression = RankingExpression.from(property.value()); + + if (instance == null) { + System.out.println(" " + name + ": " + expression); + functions.add(new ExpressionFunction(name, arguments, expression)); + } else { + System.out.println(" Binding of " + name + ": " + expression); + boundFunctions.add(new ExpressionFunction(name + instance, arguments, expression)); + } + } + else if (property.name().equals("vespa.rank.firstphase")) { // Include in addition to macros + firstPhase = new ExpressionFunction("firstphase", new ArrayList<>(), RankingExpression.from(property.value())); + } + else if (property.name().equals("vespa.rank.secondphase")) { // Include in addition to macros + secondPhase = new ExpressionFunction("secondphase", new ArrayList<>(), RankingExpression.from(property.value())); + } + } + if (functionByName("firstphase", functions) == null && firstPhase != null) // may be already included, depending on body + functions.add(firstPhase); + if (functionByName("secondphase", functions) == null && secondPhase != null) // may be already included, depending on body + functions.add(secondPhase); + return new Model(profile.name(), functions, boundFunctions); + } + + private ExpressionFunction functionByName(String name, List functions) { + for (ExpressionFunction function : functions) + if (function.getName().equals(name)) + return function; + return null; + } + +} diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankProfilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankProfilesConfigImporter.java deleted file mode 100644 index a81006d526e..00000000000 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/config/RankProfilesConfigImporter.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.models.evaluation.config; - -import ai.vespa.models.evaluation.Model; -import com.yahoo.searchlib.rankingexpression.ExpressionFunction; -import com.yahoo.searchlib.rankingexpression.RankingExpression; -import com.yahoo.vespa.config.search.RankProfilesConfig; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Converts RankProfilesConfig instances to RankingExpressions for evaluation - * - * @author bratseth - */ -public class RankProfilesConfigImporter { - - private static final Pattern expressionPattern = Pattern.compile("rankingExpression\\(([a-zA-Z0-9_]+)\\)\\.rankingScript"); - - /** - * Returns a map of the models contained in this config, indexed on name. - * The map is modifiable and owned by the caller. - */ - public Map importFrom(RankProfilesConfig config) { - Map models = new HashMap<>(); - for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { - Model model = importProfile(profile); - models.put(model.name(), model); - } - return models; - } - - private Model importProfile(RankProfilesConfig.Rankprofile profile) { - System.out.println("Importing " + profile.name()); - List functions = new ArrayList<>(); - for (RankProfilesConfig.Rankprofile.Fef.Property property : profile.fef().property()) { - Matcher expressionMatcher = expressionPattern.matcher(property.name()); - if ( ! expressionMatcher.matches()) continue; - - System.out.println(" Importing " + expressionMatcher.group(0)); - - String name = expressionMatcher.group(0); - List arguments = new ArrayList<>(); - RankingExpression expression = RankingExpression.from(property.value()); - functions.add(new ExpressionFunction(name, arguments, expression)); - } - return new Model(profile.name(), functions); - } - -} diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java new file mode 100644 index 00000000000..16ae433b958 --- /dev/null +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java @@ -0,0 +1,48 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.yahoo.config.subscription.ConfigGetter; +import com.yahoo.config.subscription.FileSource; +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.vespa.config.search.RankProfilesConfig; +import org.junit.Test; + +import java.io.File; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests instantiating models from rank-profiles configs. + * + * @author bratseth + */ +public class RankProfilesImporterTest { + + @Test + public void testImporting() { + String configPath = "src/test/resources/config/rankexpression/rank-profiles.cfg"; + RankProfilesConfig config = new ConfigGetter<>(new FileSource(new File(configPath)), RankProfilesConfig.class).getConfig(""); + Map models = new RankProfilesConfigImporter().importFrom(config); + assertEquals(18, models.size()); + Model macros = models.get("macros"); + assertNotNull(macros); + assertEquals(4, macros.functions().size()); + assertFunction("fourtimessum", "4 * (var1 + var2)", macros); + assertFunction("firstphase", "match + fieldMatch(title) + rankingExpression(myfeature)", macros); + assertFunction("secondphase", "rankingExpression(fourtimessum@5cf279212355b980.67f1e87166cfef86)", macros); + assertFunction("myfeature", + "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + " + + "30 * pow(0 - fieldMatch(description).earliness,2)", + macros); + } + + private void assertFunction(String name, String expression, Model model) { + ExpressionFunction function = model.function(name); + assertNotNull(function); + assertEquals(name, function.getName()); + assertEquals(expression, function.getBody().toString()); + } + +} diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/config/RankProfilesImporterTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/config/RankProfilesImporterTest.java deleted file mode 100644 index 2e93a1a138d..00000000000 --- a/model-inference/src/test/java/ai/vespa/models/evaluation/config/RankProfilesImporterTest.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.models.evaluation.config; - -import ai.vespa.models.evaluation.Model; -import com.yahoo.config.subscription.ConfigGetter; -import com.yahoo.config.subscription.FileSource; -import com.yahoo.searchlib.rankingexpression.ExpressionFunction; -import com.yahoo.vespa.config.search.RankProfilesConfig; -import org.junit.Test; - -import java.io.File; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -/** - * Tests instantiating models from rank-profiles configs. - * - * @author bratseth - */ -public class RankProfilesImporterTest { - - @Test - public void testRankexpression() { - String configPath = "src/test/resources/config/rankexpression/rank-profiles.cfg"; - RankProfilesConfig config = new ConfigGetter<>(new FileSource(new File(configPath)), RankProfilesConfig.class).getConfig(""); - Map models = new RankProfilesConfigImporter().importFrom(config); - assertEquals(18, models.size()); - Model macros = models.get("macros"); - assertNotNull(macros); - assertEquals(4, macros.functions().size()); - ExpressionFunction function = functionByName("fourtimessum", macros); - assertNotNull(function); - - } - - @Test - public void testRegexp() { - assertTrue("a(foo)".matches("a\\([a-zA-Z0-9_]+\\)")); - } - - private ExpressionFunction functionByName(String name, Model model) { - for (ExpressionFunction function : model.functions()) - if (function.getName().equals(name)) - return function; - return null; - } - -} -- cgit v1.2.3 From f877ef25a7ba3943e8a79ac9df4ebdd2e80f9d45 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Fri, 6 Jul 2018 13:23:58 +0200 Subject: Test bound function import --- .../src/main/java/ai/vespa/models/evaluation/Model.java | 11 ++++++++++- .../vespa/models/evaluation/RankProfilesConfigImporter.java | 10 +++------- .../ai/vespa/models/evaluation/RankProfilesImporterTest.java | 11 +++++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java index b2eb8546a7c..a3ebd16a763 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java @@ -21,6 +21,7 @@ public class Model { private final ImmutableList functions; /** An instance of each usage of the above function, where variables are replaced by their bindings */ + // TODO: Separate name and instance id? private final ImmutableList boundFunctions; public Model(String name, Collection functions) { @@ -39,7 +40,7 @@ public class Model { public List functions() { return functions; } /** Returns an immutable list of the bound function instances of this */ - List boundFunctions() { return functions; } + List boundFunctions() { return boundFunctions; } /** Returns the function withe the given name, or null if none */ // TODO: Parameter overloading? ExpressionFunction function(String name) { @@ -49,6 +50,14 @@ public class Model { return null; } + /** Returns the function withe the given name, or null if none */ // TODO: Parameter overloading? + ExpressionFunction boundFunction(String name) { + for (ExpressionFunction function : boundFunctions) + if (function.getName().equals(name)) + return function; + return null; + } + @Override public String toString() { return "Model '" + name + "'"; } diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java index 30eabb6f8a4..a09ecffeda0 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java @@ -37,7 +37,6 @@ class RankProfilesConfigImporter { } private Model importProfile(RankProfilesConfig.Rankprofile profile) { - System.out.println("Importing " + profile.name()); List functions = new ArrayList<>(); List boundFunctions = new ArrayList<>(); ExpressionFunction firstPhase = null; @@ -47,16 +46,13 @@ class RankProfilesConfigImporter { if ( expressionMatcher.matches()) { String name = expressionMatcher.group(1); String instance = expressionMatcher.group(2); - List arguments = new ArrayList<>(); + List arguments = new ArrayList<>(); // TODO: Arguments? RankingExpression expression = RankingExpression.from(property.value()); - if (instance == null) { - System.out.println(" " + name + ": " + expression); + if (instance == null) functions.add(new ExpressionFunction(name, arguments, expression)); - } else { - System.out.println(" Binding of " + name + ": " + expression); + else boundFunctions.add(new ExpressionFunction(name + instance, arguments, expression)); - } } else if (property.name().equals("vespa.rank.firstphase")) { // Include in addition to macros firstPhase = new ExpressionFunction("firstphase", new ArrayList<>(), RankingExpression.from(property.value())); diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java index 16ae433b958..977420c1352 100644 --- a/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java @@ -26,8 +26,10 @@ public class RankProfilesImporterTest { RankProfilesConfig config = new ConfigGetter<>(new FileSource(new File(configPath)), RankProfilesConfig.class).getConfig(""); Map models = new RankProfilesConfigImporter().importFrom(config); assertEquals(18, models.size()); + Model macros = models.get("macros"); assertNotNull(macros); + assertEquals("macros", macros.name()); assertEquals(4, macros.functions().size()); assertFunction("fourtimessum", "4 * (var1 + var2)", macros); assertFunction("firstphase", "match + fieldMatch(title) + rankingExpression(myfeature)", macros); @@ -36,6 +38,8 @@ public class RankProfilesImporterTest { "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + " + "30 * pow(0 - fieldMatch(description).earliness,2)", macros); + assertEquals(1, macros.boundFunctions().size()); + assertBoundFunction("fourtimessum@5cf279212355b980.67f1e87166cfef86", "4 * (match + rankBoost)", macros); } private void assertFunction(String name, String expression, Model model) { @@ -45,4 +49,11 @@ public class RankProfilesImporterTest { assertEquals(expression, function.getBody().toString()); } + private void assertBoundFunction(String name, String expression, Model model) { + ExpressionFunction function = model.boundFunction(name); + assertNotNull(function); + assertEquals(name, function.getName()); + assertEquals(expression, function.getBody().toString()); + } + } -- cgit v1.2.3 From 76cd21c9537a488537dc22cde13c08b137f54837 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Fri, 6 Jul 2018 14:43:06 +0200 Subject: Evaluation tests --- .../java/ai/vespa/models/evaluation/Model.java | 13 ++++- .../vespa/models/evaluation/ModelsEvaluator.java | 34 ++++++++++++- .../models/evaluation/ModelsEvaluatorTest.java | 55 +++++++++++++++++++++- .../rankingexpression/RankingExpression.java | 12 ++--- .../rankingexpression/evaluation/Value.java | 8 ++++ 5 files changed, 113 insertions(+), 9 deletions(-) diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java index a3ebd16a763..cbeb1ca708c 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java @@ -7,6 +7,7 @@ import com.yahoo.searchlib.rankingexpression.ExpressionFunction; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** * A named collection of functions @@ -39,6 +40,16 @@ public class Model { /** Returns an immutable list of the free (callable) functions of this */ public List functions() { return functions; } + /** Returns the given function, or throws a IllegalArgumentException if it does not exist */ + ExpressionFunction requireFunction(String name) { + ExpressionFunction function = function(name); + if (function == null) + throw new IllegalArgumentException("No function named '" + name + " in " + this + ". Available functions: " + + functions.stream().map(f -> f.getName()).collect(Collectors.joining(", "))); + return function; + } + + /** Returns an immutable list of the bound function instances of this */ List boundFunctions() { return boundFunctions; } @@ -59,6 +70,6 @@ public class Model { } @Override - public String toString() { return "Model '" + name + "'"; } + public String toString() { return "model '" + name + "'"; } } diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java index 3532409766b..6d1f0d885ae 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java @@ -1,19 +1,51 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.models.evaluation; +import com.google.common.collect.ImmutableMap; +import com.yahoo.searchlib.rankingexpression.evaluation.Context; +import com.yahoo.tensor.Tensor; import com.yahoo.vespa.config.search.RankProfilesConfig; +import java.util.Map; +import java.util.stream.Collectors; + /** * Evaluates machine-learned models added to Vespa applications and available as config form. + * TODO: Write JavaDoc similar to RankingExpression * * @author bratseth */ public class ModelsEvaluator { + private final ImmutableMap models; + public ModelsEvaluator(RankProfilesConfig config) { - new RankProfilesConfigImporter().importFrom(config); + models = ImmutableMap.copyOf(new RankProfilesConfigImporter().importFrom(config)); } + /** Returns the models of this as an immutable map */ + public Map models() { return models; } + /** + * Evaluates the given function in the given model. + * + * @param modelName the name of the model to evaluate + * @param functionName the function to evaluate in the model + * @param context the evaluation context which provides bindings of the arguments to the function + * @return the tensor resulting from evaluating the model, never null + * @throws IllegalArgumentException if the model of function is not present + */ + public Tensor evaluate(String modelName, String functionName, Context context) { + return requireModel(modelName).requireFunction(functionName).getBody().evaluate(context).asTensor(); + } + + /** Returns the given model, or throws a IllegalArgumentException if it does not exist */ + public Model requireModel(String name) { + Model model = models.get(name); + if (model == null) + throw new IllegalArgumentException("No model named '" + name + ". Available models: " + + models.keySet().stream().collect(Collectors.joining(", "))); + return model; + } } diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java index 68c7b2dc3cf..13b6dbb6dd9 100644 --- a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java @@ -1,13 +1,66 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.models.evaluation; +import com.yahoo.config.subscription.ConfigGetter; +import com.yahoo.config.subscription.FileSource; +import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext; +import com.yahoo.searchlib.rankingexpression.evaluation.MapContext; +import com.yahoo.searchlib.rankingexpression.evaluation.Value; +import com.yahoo.tensor.Tensor; +import com.yahoo.vespa.config.search.RankProfilesConfig; import org.junit.Test; +import java.io.File; + +import static org.junit.Assert.assertEquals; + +/** + * @author bratseth + */ public class ModelsEvaluatorTest { + private static final double delta = 0.00000000001; + + private ModelsEvaluator createEvaluator() { + String configPath = "src/test/resources/config/rankexpression/rank-profiles.cfg"; + RankProfilesConfig config = new ConfigGetter<>(new FileSource(new File(configPath)), RankProfilesConfig.class).getConfig(""); + return new ModelsEvaluator(config); + } + @Test - public void testEvaluation() { + public void testScalarMapContextEvaluation() { + ModelsEvaluator evaluator = createEvaluator(); + MapContext context = new MapContext(); + context.put("var1", 3); + context.put("var2", 5); + assertEquals(32.0, evaluator.evaluate("macros", "fourtimessum", context).asDouble(), delta); + } + @Test + public void testTensorMapContextEvaluation() { + ModelsEvaluator evaluator = createEvaluator(); + MapContext context = new MapContext(); + context.put("var1", Value.of(Tensor.from("{{x:0}:3,{x:1}:5}"))); + context.put("var2", Value.of(Tensor.from("{{x:0}:7,{x:1}:11}"))); + assertEquals(Tensor.from("{{x:0}:40.0,{x:1}:64.0}"), evaluator.evaluate("macros", "fourtimessum", context)); + } + + @Test + public void testScalarArrayCnotextEvaluation() { + ModelsEvaluator evaluator = createEvaluator(); + ArrayContext context = new ArrayContext(evaluator.requireModel("macros").requireFunction("fourtimessum").getBody()); + context.put("var1", Value.of(Tensor.from("{{x:0}:3,{x:1}:5}"))); + context.put("var2", Value.of(Tensor.from("{{x:0}:7,{x:1}:11}"))); + assertEquals(Tensor.from("{{x:0}:40.0,{x:1}:64.0}"), evaluator.evaluate("macros", "fourtimessum", context)); + } + + @Test + public void testTensorArrayContextEvaluation() { + ModelsEvaluator evaluator = createEvaluator(); + ArrayContext context = new ArrayContext(evaluator.requireModel("macros").requireFunction("fourtimessum").getBody()); + context.put("var1", Value.of(Tensor.from("{{x:0}:3,{x:1}:5}"))); + context.put("var2", Value.of(Tensor.from("{{x:0}:7,{x:1}:11}"))); + assertEquals(Tensor.from("{{x:0}:40.0,{x:1}:64.0}"), evaluator.evaluate("macros", "fourtimessum", context)); } } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java index 6fa5b1196fa..aec5b2367bf 100755 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java @@ -57,10 +57,10 @@ ArrayContext contextPrototype; // Create reusable, gbdt optimized expression and context. // The expression is multithread-safe while the context created is not try { - RankingExpression expression=new RankingExpression("10*if(i>35,if(i>one,if(i>=670,4,8),if(i>8000,5,3)),if(i==478,90,91))"); - ArrayContext contextPrototype=new ArrayContext(expression); - ExpressionOptimizer optimizer=new ExpressionOptimizer(); // Increases evaluation speed of gbdt form expressions by 3-4x - OptimizationReport triviaAboutTheOptimization=optimizer.optimize(expression,contextPrototype); + RankingExpression expression = new RankingExpression("10*if(i>35,if(i>one,if(i>=670,4,8),if(i>8000,5,3)),if(i==478,90,91))"); + ArrayContext contextPrototype = new ArrayContext(expression); + ExpressionOptimizer optimizer = new ExpressionOptimizer(); // Increases evaluation speed of gbdt form expressions by 3-4x + OptimizationReport triviaAboutTheOptimization = optimizer.optimize(expression,contextPrototype); } catch (ParseException e) { throw new RuntimeException(e); @@ -69,9 +69,9 @@ catch (ParseException e) { ... // Execution (many) -context=contextPrototype.clone(); // If evaluation is multithreaded - skip this if execution is single-threaded +context = contextPrototype.clone(); // If evaluation is multithreaded - skip this if execution is single-threaded context.put("one",1d); -double result=expression.evaluate(context); +double result = expression.evaluate(context); * * @author Simon Thoresen diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java index e5a9e6a5ef1..7809cdd4e1b 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java @@ -115,4 +115,12 @@ public abstract class Value { return new DoubleValue(Double.parseDouble(value)); } + public static Value of(Tensor tensor) { + return new TensorValue(tensor); + } + + public static Value of(double scalar) { + return new DoubleValue(scalar); + } + } -- cgit v1.2.3 From 7da071be1acf39adacb8b31b9922a6e3754a279b Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Fri, 20 Jul 2018 09:41:04 +0200 Subject: Java model evaluation WIP --- .../main/java/com/yahoo/search/query/Model.java | 8 +- .../main/java/com/yahoo/language/Linguistics.java | 7 +- .../vespa/models/evaluation/LazyArrayContext.java | 141 ++++++++++++++++++++ .../java/ai/vespa/models/evaluation/LazyValue.java | 146 +++++++++++++++++++++ .../java/ai/vespa/models/evaluation/Model.java | 22 +++- .../vespa/models/evaluation/ModelsEvaluator.java | 9 ++ .../evaluation/RankProfilesConfigImporter.java | 3 +- .../models/evaluation/ModelsEvaluatorTest.java | 12 +- .../evaluation/AbstractArrayContext.java | 146 +++++++++++++-------- .../rankingexpression/evaluation/ArrayContext.java | 2 +- .../rankingexpression/evaluation/ContextIndex.java | 20 +++ .../evaluation/ExpressionOptimizer.java | 2 +- .../rankingexpression/evaluation/Optimizer.java | 2 +- .../gbdtoptimization/GBDTForestOptimizer.java | 5 +- .../evaluation/gbdtoptimization/GBDTOptimizer.java | 12 +- 15 files changed, 457 insertions(+), 80 deletions(-) create mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java create mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/LazyValue.java create mode 100644 searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ContextIndex.java diff --git a/container-search/src/main/java/com/yahoo/search/query/Model.java b/container-search/src/main/java/com/yahoo/search/query/Model.java index dc7a61344cb..167bb312f61 100644 --- a/container-search/src/main/java/com/yahoo/search/query/Model.java +++ b/container-search/src/main/java/com/yahoo/search/query/Model.java @@ -78,11 +78,11 @@ public class Model implements Cloneable { private String defaultIndex = null; private Query.Type type = Query.Type.ALL; private Query parent; - private Set sources=new LinkedHashSet<>(); - private Set restrict=new LinkedHashSet<>(); + private Set sources = new LinkedHashSet<>(); + private Set restrict = new LinkedHashSet<>(); private String searchPath; private String documentDbName = null; - private Execution execution=new Execution(new Execution.Context(null, null, null, null, null)); + private Execution execution = new Execution(new Execution.Context(null, null, null, null, null)); public Model(Query query) { setParent(query); @@ -101,7 +101,7 @@ public class Model implements Cloneable { */ @Deprecated public void traceLanguage() { - if (getParent().getTraceLevel()<2) return; + if (getParent().getTraceLevel() < 2) return; if (language != null) { getParent().trace("Language " + getLanguage() + " specified directly as a parameter", false, 2); } diff --git a/linguistics/src/main/java/com/yahoo/language/Linguistics.java b/linguistics/src/main/java/com/yahoo/language/Linguistics.java index e275f189b0c..c3c0c049e99 100644 --- a/linguistics/src/main/java/com/yahoo/language/Linguistics.java +++ b/linguistics/src/main/java/com/yahoo/language/Linguistics.java @@ -41,7 +41,12 @@ public interface Linguistics { CHARACTER_CLASSES } - /** The same as new com.yahoo.language.simple.SimpleLinguistics(). Prefer using that directly. */ + /** + * The same as new com.yahoo.language.simple.SimpleLinguistics(). Prefer using that directly. + * + * @deprecated use new com.yahoo.language.simple.SimpleLinguistics() + */ + @Deprecated // TODO: Remove this field on Vespa 7 Linguistics SIMPLE = new SimpleLinguistics(); /** diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java new file mode 100644 index 00000000000..59dd1fd7b12 --- /dev/null +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java @@ -0,0 +1,141 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.RankingExpression; +import com.yahoo.searchlib.rankingexpression.Reference; +import com.yahoo.searchlib.rankingexpression.evaluation.AbstractArrayContext; +import com.yahoo.searchlib.rankingexpression.evaluation.Context; +import com.yahoo.searchlib.rankingexpression.evaluation.ContextIndex; +import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue; +import com.yahoo.searchlib.rankingexpression.evaluation.Value; +import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode; +import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; +import com.yahoo.tensor.TensorType; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +/** + * An array context supporting functions invocations implemented as lazy values. + * + * @author bratseth + */ +class LazyArrayContext extends Context implements ContextIndex { + + /** The current values set */ + private Value[] values; + + private static DoubleValue constantZero = DoubleValue.frozen(0); + + /** + * Create a fast lookup, lazy context for an expression. + * This instance should be reused indefinitely by a single thread. + * + * @param expression the expression to create a context for + */ + LazyArrayContext(RankingExpression expression, List functions) { + values = new Value[doubleValues().length]; + Arrays.fill(values, DoubleValue.zero); + } + + @Override + protected void extractBindTargets(ExpressionNode node, Set bindTargets) { + if (isFunctionReference(node)) { + ReferenceNode reference = (ReferenceNode)node; + bindTargets.add(reference.getArguments().expressions().get(0).toString()); + + } + else { + super.extractBindTargets(node, bindTargets); + } + } + + private boolean isFunctionReference(ExpressionNode node) { + if ( ! (node instanceof ReferenceNode)) return false; + + ReferenceNode reference = (ReferenceNode)node; + return reference.getName().equals("rankingExpression") && reference.getArguments().size() == 1; + } + + /** + * Puts a value by name. + * The value will be frozen if it isn't already. + * + * @throws IllegalArgumentException if the name is not present in the ranking expression this was created with, and + * ignoredUnknownValues is false + */ + @Override + public final void put(String name, Value value) { + Integer index = nameToIndex().get(name); + if (index == null) { + if (ignoreUnknownValues()) + return; + else + throw new IllegalArgumentException("Value '" + name + "' is not known to " + this); + } + put(index, value); + } + + /** Same as put(index,DoubleValue.frozen(value)) */ + public final void put(int index, double value) { + put(index, DoubleValue.frozen(value)); + } + + /** + * Puts a value by index. + * The value will be frozen if it isn't already. + */ + public final void put(int index, Value value) { + values[index] = value.freeze(); + try { + doubleValues()[index] = value.asDouble(); + } + catch (UnsupportedOperationException e) { + doubleValues()[index] = Double.NaN; // see getDouble below + } + } + + @Override + public TensorType getType(Reference reference) { + Integer index = nameToIndex().get(reference.toString()); + if (index == null) return null; + return values[index].type(); + } + + /** Perform a slow lookup by name */ + @Override + public Value get(String name) { + Integer index = nameToIndex().get(name); + if (index == null) return DoubleValue.zero; + return values[index]; + } + + /** Perform a fast lookup by index */ + @Override + public final Value get(int index) { + return values[index]; + } + + /** Perform a fast lookup directly of the value as a double. This is faster than get(index).asDouble() */ + @Override + public final double getDouble(int index) { + double value = doubleValues()[index]; + if (value == Double.NaN) + throw new UnsupportedOperationException("Value at " + index + " has no double representation"); + return value; + } + + /** + * Creates a clone of this context suitable for evaluating against the same ranking expression + * in a different thread (i.e, name name to index map, different value set. + */ + public LazyArrayContext clone() { + LazyArrayContext clone = (LazyArrayContext)super.clone(); + clone.values = new Value[nameToIndex().size()]; + Arrays.fill(values, constantZero); + return clone; + } + +} diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyValue.java b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyValue.java new file mode 100644 index 00000000000..b026007346d --- /dev/null +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyValue.java @@ -0,0 +1,146 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.evaluation.Context; +import com.yahoo.searchlib.rankingexpression.evaluation.Value; +import com.yahoo.searchlib.rankingexpression.rule.Function; +import com.yahoo.searchlib.rankingexpression.rule.TruthOperator; +import com.yahoo.tensor.Tensor; +import com.yahoo.tensor.TensorType; + +/** + * A Value which is computed from an expression when first requested. + * This is not multithread safe. + * + * @author bratseth + */ +class LazyValue extends Value { + + /** The function computing the value of this */ + private final ExpressionFunction function; + + /** The context used to compute the function of this */ + private final Context context; + + private Value computedValue = null; + + public LazyValue(ExpressionFunction function, Context context) { + this.function = function; + this.context = context; + } + + private Value computedValue() { + if (computedValue == null) + computedValue = function.getBody().evaluate(context); + return computedValue; + } + + @Override + public TensorType type() { + return computedValue().type(); // TODO: Keep type information in this/ExpressionFunction to avoid computing here + } + + @Override + public double asDouble() { + return computedValue().asDouble(); + } + + @Override + public Tensor asTensor() { + return computedValue().asTensor(); + } + + @Override + public boolean hasDouble() { + return type().rank() == 0; + } + + @Override + public boolean asBoolean() { + return computedValue().asBoolean(); + } + + @Override + public Value negate() { + return computedValue().negate(); + } + + @Override + public Value add(Value value) { + return computedValue().add(value); + } + + @Override + public Value subtract(Value value) { + return computedValue().subtract(value); + } + + @Override + public Value multiply(Value value) { + return computedValue().multiply(value); + } + + @Override + public Value divide(Value value) { + return computedValue().divide(value); + } + + @Override + public Value modulo(Value value) { + return computedValue().modulo(value); + } + + @Override + public Value and(Value value) { + return computedValue().and(value); + } + + @Override + public Value or(Value value) { + return computedValue().or(value); + } + + @Override + public Value not() { + return computedValue().not(); + } + + @Override + public Value power(Value value) { + return computedValue().power(value); + } + + @Override + public Value compare(TruthOperator operator, Value value) { + return computedValue().compare(operator, value); + } + + @Override + public Value function(Function function, Value value) { + return computedValue().function(function, value); + } + + @Override + public Value asMutable() { + return computedValue().asMutable(); + } + + @Override + public String toString() { + return "value of " + function; + } + + @Override + public boolean equals(Object other) { + if (other == this) return true; + if (!(other instanceof Value)) return false; + return computedValue().equals(other); + } + + @Override + public int hashCode() { + return computedValue().hashCode(); + } + +} diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java index cbeb1ca708c..5bccd526571 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java @@ -3,6 +3,8 @@ package ai.vespa.models.evaluation; import com.google.common.collect.ImmutableList; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext; +import com.yahoo.searchlib.rankingexpression.evaluation.Context; import java.util.Collection; import java.util.Collections; @@ -50,9 +52,6 @@ public class Model { } - /** Returns an immutable list of the bound function instances of this */ - List boundFunctions() { return boundFunctions; } - /** Returns the function withe the given name, or null if none */ // TODO: Parameter overloading? ExpressionFunction function(String name) { for (ExpressionFunction function : functions) @@ -61,6 +60,9 @@ public class Model { return null; } + /** Returns an immutable list of the bound function instances of this */ + List boundFunctions() { return boundFunctions; } + /** Returns the function withe the given name, or null if none */ // TODO: Parameter overloading? ExpressionFunction boundFunction(String name) { for (ExpressionFunction function : boundFunctions) @@ -69,6 +71,20 @@ public class Model { return null; } + /** + * Returns a function which can be used to evaluate the given function + * + * @throws IllegalArgumentException if the function is not present + */ + public Context contextFor(String function) { + Context context = new LazyArrayContext(requireFunction(function).getBody(), boundFunctions); + for (ExpressionFunction boundFunction : boundFunctions) { + System.out.println("Binding " + boundFunction.getName()); + context.put(boundFunction.getName(), new LazyValue(boundFunction, context)); + } + return context; + } + @Override public String toString() { return "model '" + name + "'"; } diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java index 6d1f0d885ae..35c6a269edd 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java @@ -26,6 +26,15 @@ public class ModelsEvaluator { /** Returns the models of this as an immutable map */ public Map models() { return models; } + /** + * Returns a function which can be used to evaluate the given function in the given model + * + * @throws IllegalArgumentException if the function or model is not present + */ + public Context contextFor(String modelName, String functionName) { + return requireModel(modelName).contextFor(functionName); + } + /** * Evaluates the given function in the given model. * diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java index a09ecffeda0..15d3ab4bf1f 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java @@ -20,7 +20,8 @@ import java.util.regex.Pattern; */ class RankProfilesConfigImporter { - private static final Pattern expressionPattern = + // TODO: Move to separate class ... or something + static final Pattern expressionPattern = Pattern.compile("rankingExpression\\(([a-zA-Z0-9_]+)(@[a-f0-9]+\\.[a-f0-9]+)?\\)\\.rankingScript"); /** diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java index 13b6dbb6dd9..9965a3c86ba 100644 --- a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java @@ -4,6 +4,7 @@ package ai.vespa.models.evaluation; import com.yahoo.config.subscription.ConfigGetter; import com.yahoo.config.subscription.FileSource; import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext; +import com.yahoo.searchlib.rankingexpression.evaluation.Context; import com.yahoo.searchlib.rankingexpression.evaluation.MapContext; import com.yahoo.searchlib.rankingexpression.evaluation.Value; import com.yahoo.tensor.Tensor; @@ -46,7 +47,7 @@ public class ModelsEvaluatorTest { } @Test - public void testScalarArrayCnotextEvaluation() { + public void testScalarArrayContextEvaluation() { ModelsEvaluator evaluator = createEvaluator(); ArrayContext context = new ArrayContext(evaluator.requireModel("macros").requireFunction("fourtimessum").getBody()); context.put("var1", Value.of(Tensor.from("{{x:0}:3,{x:1}:5}"))); @@ -63,4 +64,13 @@ public class ModelsEvaluatorTest { assertEquals(Tensor.from("{{x:0}:40.0,{x:1}:64.0}"), evaluator.evaluate("macros", "fourtimessum", context)); } + @Test + public void testEvaluationDependingOnBoundMacro() { + ModelsEvaluator evaluator = createEvaluator(); + Context context = evaluator.contextFor("macros", "secondphase"); + context.put("match", 3); + context.put("rankboost", 5); + assertEquals(32.0, evaluator.evaluate("macros", "secondphase", context).asDouble(), delta); + } + } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java index ed9fa346c11..893e31c7087 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java @@ -7,8 +7,6 @@ import com.yahoo.searchlib.rankingexpression.rule.CompositeNode; import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode; import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; -import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -20,19 +18,15 @@ import java.util.Set; * * @author bratseth */ -public abstract class AbstractArrayContext extends Context implements Cloneable { +public abstract class AbstractArrayContext extends Context implements Cloneable, ContextIndex { private final boolean ignoreUnknownValues; - /** The mapping from variable name to index */ - private final ImmutableMap nameToIndex; - - /** The current values set, pre-converted to doubles */ - private double[] doubleValues; - /** The name of the ranking expression this was created for */ private final String rankingExpressionName; + private IndexedBindings indexedBindings; + /** * Create a fast lookup context for an expression. * This instance should be reused indefinitely by a single thread. @@ -53,44 +47,50 @@ public abstract class AbstractArrayContext extends Context implements Cloneable protected AbstractArrayContext(RankingExpression expression, boolean ignoreUnknownValues) { this.ignoreUnknownValues = ignoreUnknownValues; this.rankingExpressionName = expression.getName(); - Set variables = new LinkedHashSet<>(); - extractVariables(expression.getRoot(),variables); + this.indexedBindings = new IndexedBindings(expression); + } - doubleValues = new double[variables.size()]; + protected final Map nameToIndex() { return indexedBindings.nameToIndex(); } + protected final double[] doubleValues() { return indexedBindings.doubleValues(); } + protected final boolean ignoreUnknownValues() { return ignoreUnknownValues; } - int i = 0; - ImmutableMap.Builder nameToIndexBuilder = new ImmutableMap.Builder<>(); - for (String variable : variables) - nameToIndexBuilder.put(variable,i++); - nameToIndex = nameToIndexBuilder.build(); + public Set names() { + return indexedBindings.names(); } - private void extractVariables(ExpressionNode node,Set variables) { - if (node instanceof ReferenceNode) { - ReferenceNode fNode=(ReferenceNode)node; - if (fNode.getArguments().expressions().size()>0) - throw new UnsupportedOperationException("Array lookup is not supported with features having arguments)"); - variables.add(fNode.toString()); - } - else if (node instanceof CompositeNode) { - CompositeNode cNode=(CompositeNode)node; - for (ExpressionNode child : cNode.children()) - extractVariables(child,variables); - } + /** + * Returns the index from a name. + * + * @throws NullPointerException is this name is not known to this context + */ + @Override + public final int getIndex(String name) { return indexedBindings.nameToIndex.get(name); } + + /** Returns the max number of variables which may be set in this */ + @Override + public int size() { return indexedBindings.size(); } + + /** Perform a fast lookup directly of the value as a double. This is faster than get(index).asDouble() */ + @Override + public double getDouble(int index) { + return indexedBindings.getDouble(index); } - protected final Map nameToIndex() { return nameToIndex; } - protected final double[] doubleValues() { return doubleValues; } - protected final boolean ignoreUnknownValues() { return ignoreUnknownValues; } + @Override + public String toString() { + return "fast lookup context for ranking expression '" + rankingExpressionName + + "' [" + size() + " variables]"; + } /** * Creates a clone of this context suitable for evaluating against the same ranking expression * in a different thread (i.e, name name to index map, different value set. */ + @Override public AbstractArrayContext clone() { try { - AbstractArrayContext clone=(AbstractArrayContext)super.clone(); - clone.doubleValues=new double[nameToIndex.size()]; + AbstractArrayContext clone = (AbstractArrayContext)super.clone(); + clone.indexedBindings = indexedBindings.clone(); return clone; } catch (CloneNotSupportedException e) { @@ -98,34 +98,64 @@ public abstract class AbstractArrayContext extends Context implements Cloneable } } - public Set names() { - return nameToIndex.keySet(); - } + private static class IndexedBindings implements Cloneable { - /** - * Returns the index from a name. - * - * @throws NullPointerException is this name is not known to this context - */ - public final int getIndex(String name) { - return nameToIndex.get(name); - } + /** The mapping from variable name to index */ + private final ImmutableMap nameToIndex; - /** Returns the max number of variables which may be set in this */ - public int size() { - return doubleValues.length; - } + /** The current values set, pre-converted to doubles */ + private double[] doubleValues; - /** Perform a fast lookup directly of the value as a double. This is faster than get(index).asDouble() */ - @Override - public double getDouble(int index) { - return doubleValues[index]; - } + public IndexedBindings(RankingExpression expression) { + Set bindTargets = new LinkedHashSet<>(); + extractBindTargets(expression.getRoot(), bindTargets); + + doubleValues = new double[bindTargets.size()]; + + int i = 0; + ImmutableMap.Builder nameToIndexBuilder = new ImmutableMap.Builder<>(); + for (String variable : bindTargets) + nameToIndexBuilder.put(variable,i++); + nameToIndex = nameToIndexBuilder.build(); + } + + private void extractBindTargets(ExpressionNode node, Set bindTargets) { + if (node instanceof ReferenceNode) { + if (((ReferenceNode)node).getArguments().expressions().size() > 0) + throw new UnsupportedOperationException("Can not bind " + node + + ": Array lookup is not supported with features having arguments)"); + bindTargets.add(node.toString()); + } + else if (node instanceof CompositeNode) { + CompositeNode cNode = (CompositeNode)node; + for (ExpressionNode child : cNode.children()) + extractBindTargets(child, bindTargets); + } + } + + public Map nameToIndex() { return nameToIndex; } + public double[] doubleValues() { return doubleValues; } + public Set names() { return nameToIndex.keySet(); } + public int getIndex(String name) { return nameToIndex.get(name); } + public int size() { return doubleValues.length; } + public double getDouble(int index) { return doubleValues[index]; } + + /** + * Creates a clone of this context suitable for evaluating against the same ranking expression + * in a different thread (i.e, name name to index map, different value set. + */ + @Override + public IndexedBindings clone() { + try { + IndexedBindings clone = (IndexedBindings)super.clone(); + clone.doubleValues = new double[nameToIndex.size()]; + return clone; + } + catch (CloneNotSupportedException e) { + throw new RuntimeException("Programming error"); + } + } - @Override - public String toString() { - return "fast lookup context for ranking expression '" + rankingExpressionName + - "' [" + doubleValues.length + " variables]"; } } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java index ee5952d9aea..237c3a1d0b1 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java @@ -119,7 +119,7 @@ public class ArrayContext extends AbstractArrayContext implements Cloneable { public ArrayContext clone() { ArrayContext clone = (ArrayContext)super.clone(); clone.values = new Value[nameToIndex().size()]; - Arrays.fill(values,constantZero); + Arrays.fill(values, constantZero); return clone; } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ContextIndex.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ContextIndex.java new file mode 100644 index 00000000000..4f1465cd1f5 --- /dev/null +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ContextIndex.java @@ -0,0 +1,20 @@ +package com.yahoo.searchlib.rankingexpression.evaluation; + +/** + * Indexed context lookup methods + * + * @author bratseth + */ +public interface ContextIndex { + + /** Returns the number of bound variables in this */ + int size(); + + /** + * Returns the index from a name. + * + * @throws NullPointerException is this name is not known to this context + */ + int getIndex(String name); + +} diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ExpressionOptimizer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ExpressionOptimizer.java index 836aadd9f70..b82173eabd5 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ExpressionOptimizer.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ExpressionOptimizer.java @@ -44,7 +44,7 @@ public class ExpressionOptimizer { return null; } - public OptimizationReport optimize(RankingExpression expression, AbstractArrayContext arrayContext) { + public OptimizationReport optimize(RankingExpression expression, ContextIndex arrayContext) { OptimizationReport report = new OptimizationReport(); // Note: Order of optimizations matter gbdtOptimizer.optimize(expression, arrayContext, report); diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Optimizer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Optimizer.java index fd9c02100f7..044b5b589a5 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Optimizer.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Optimizer.java @@ -18,6 +18,6 @@ public abstract class Optimizer { /** Returns whether this is enabled */ public boolean isEnabled() { return enabled; } - public abstract void optimize(RankingExpression expression, AbstractArrayContext context, OptimizationReport report); + public abstract void optimize(RankingExpression expression, ContextIndex context, OptimizationReport report); } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizer.java index 8999be4745a..bb8b91eecab 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizer.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizer.java @@ -2,8 +2,7 @@ package com.yahoo.searchlib.rankingexpression.evaluation.gbdtoptimization; import com.yahoo.searchlib.rankingexpression.RankingExpression; -import com.yahoo.searchlib.rankingexpression.evaluation.AbstractArrayContext; -import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext; +import com.yahoo.searchlib.rankingexpression.evaluation.ContextIndex; import com.yahoo.searchlib.rankingexpression.evaluation.OptimizationReport; import com.yahoo.searchlib.rankingexpression.evaluation.Optimizer; import com.yahoo.searchlib.rankingexpression.rule.ArithmeticNode; @@ -34,7 +33,7 @@ public class GBDTForestOptimizer extends Optimizer { * @param report the optimization report to which actions of this is logged */ @Override - public void optimize(RankingExpression expression, AbstractArrayContext context, OptimizationReport report) { + public void optimize(RankingExpression expression, ContextIndex context, OptimizationReport report) { if ( ! isEnabled()) return; this.report = report; diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizer.java index 74af3e576c1..787818b0f42 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizer.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizer.java @@ -33,7 +33,7 @@ public class GBDTOptimizer extends Optimizer { * @param report the optimization report to which actions of this is logged */ @Override - public void optimize(RankingExpression expression, AbstractArrayContext context, OptimizationReport report) { + public void optimize(RankingExpression expression, ContextIndex context, OptimizationReport report) { if (!isEnabled()) return; this.report = report; @@ -60,7 +60,7 @@ public class GBDTOptimizer extends Optimizer { * * @return the optimized expression */ - private ExpressionNode optimize(ExpressionNode node, AbstractArrayContext context) { + private ExpressionNode optimize(ExpressionNode node, ContextIndex context) { if (node instanceof ArithmeticNode) { Iterator childIt = ((ArithmeticNode)node).children().iterator(); ExpressionNode ret = optimize(childIt.next(), context); @@ -77,7 +77,7 @@ public class GBDTOptimizer extends Optimizer { return node; } - private ExpressionNode createGBDTNode(IfNode cNode, AbstractArrayContext context) { + private ExpressionNode createGBDTNode(IfNode cNode,ContextIndex context) { List values = new ArrayList<>(); try { consumeNode(cNode, values, context); @@ -93,7 +93,7 @@ public class GBDTOptimizer extends Optimizer { /** * Recursively consume nodes into the value list Returns the number of values produced by this. */ - private int consumeNode(ExpressionNode node, List values, AbstractArrayContext context) { + private int consumeNode(ExpressionNode node, List values, ContextIndex context) { int beforeIndex = values.size(); if ( node instanceof IfNode) { IfNode ifNode = (IfNode)node; @@ -113,7 +113,7 @@ public class GBDTOptimizer extends Optimizer { } /** Consumes the if condition and return the size of the values resulting, for convenience */ - private int consumeIfCondition(ExpressionNode condition, List values, AbstractArrayContext context) { + private int consumeIfCondition(ExpressionNode condition, List values, ContextIndex context) { if (condition instanceof ComparisonNode) { ComparisonNode comparison = (ComparisonNode)condition; if (comparison.getOperator() == TruthOperator.SMALLER) @@ -138,7 +138,7 @@ public class GBDTOptimizer extends Optimizer { return values.size(); } - private double getVariableIndex(ExpressionNode node, AbstractArrayContext context) { + private double getVariableIndex(ExpressionNode node, ContextIndex context) { if (!(node instanceof ReferenceNode)) { throw new IllegalArgumentException("Contained a left-hand comparison expression " + "which was not a feature value but was: " + node); -- cgit v1.2.3 From 89496953aa57fb86465e554c009e38a0fff83577 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Fri, 20 Jul 2018 12:48:23 +0200 Subject: Basic Java model evaluation --- .../vespa/models/evaluation/LazyArrayContext.java | 190 ++++++++++++++------- .../java/ai/vespa/models/evaluation/Model.java | 32 ++-- .../evaluation/RankProfilesConfigImporter.java | 35 ++-- .../models/evaluation/ModelsEvaluatorTest.java | 2 +- .../evaluation/RankProfilesImporterTest.java | 11 +- .../rankingexpression/RankingExpression.java | 2 +- .../evaluation/AbstractArrayContext.java | 1 + .../rankingexpression/evaluation/ContextIndex.java | 8 +- 8 files changed, 180 insertions(+), 101 deletions(-) diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java index 59dd1fd7b12..609010b22af 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java @@ -1,6 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.models.evaluation; +import com.google.common.collect.ImmutableMap; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; import com.yahoo.searchlib.rankingexpression.RankingExpression; import com.yahoo.searchlib.rankingexpression.Reference; @@ -9,12 +10,15 @@ import com.yahoo.searchlib.rankingexpression.evaluation.Context; import com.yahoo.searchlib.rankingexpression.evaluation.ContextIndex; import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue; import com.yahoo.searchlib.rankingexpression.evaluation.Value; +import com.yahoo.searchlib.rankingexpression.rule.CompositeNode; import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode; import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; import com.yahoo.tensor.TensorType; import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -22,41 +26,19 @@ import java.util.Set; * * @author bratseth */ -class LazyArrayContext extends Context implements ContextIndex { +final class LazyArrayContext extends Context implements ContextIndex { - /** The current values set */ - private Value[] values; - - private static DoubleValue constantZero = DoubleValue.frozen(0); + private final String expressionName; + private IndexedBindings indexedBindings; /** * Create a fast lookup, lazy context for an expression. - * This instance should be reused indefinitely by a single thread. * * @param expression the expression to create a context for */ - LazyArrayContext(RankingExpression expression, List functions) { - values = new Value[doubleValues().length]; - Arrays.fill(values, DoubleValue.zero); - } - - @Override - protected void extractBindTargets(ExpressionNode node, Set bindTargets) { - if (isFunctionReference(node)) { - ReferenceNode reference = (ReferenceNode)node; - bindTargets.add(reference.getArguments().expressions().get(0).toString()); - - } - else { - super.extractBindTargets(node, bindTargets); - } - } - - private boolean isFunctionReference(ExpressionNode node) { - if ( ! (node instanceof ReferenceNode)) return false; - - ReferenceNode reference = (ReferenceNode)node; - return reference.getName().equals("rankingExpression") && reference.getArguments().size() == 1; + LazyArrayContext(RankingExpression expression, Map functions) { + this.expressionName = expression.getName(); + this.indexedBindings = new IndexedBindings(expression, functions, this); } /** @@ -67,15 +49,8 @@ class LazyArrayContext extends Context implements ContextIndex { * ignoredUnknownValues is false */ @Override - public final void put(String name, Value value) { - Integer index = nameToIndex().get(name); - if (index == null) { - if (ignoreUnknownValues()) - return; - else - throw new IllegalArgumentException("Value '" + name + "' is not known to " + this); - } - put(index, value); + public void put(String name, Value value) { + put(requireIndexOf(name), value); } /** Same as put(index,DoubleValue.frozen(value)) */ @@ -87,55 +62,150 @@ class LazyArrayContext extends Context implements ContextIndex { * Puts a value by index. * The value will be frozen if it isn't already. */ - public final void put(int index, Value value) { - values[index] = value.freeze(); - try { - doubleValues()[index] = value.asDouble(); - } - catch (UnsupportedOperationException e) { - doubleValues()[index] = Double.NaN; // see getDouble below - } + public void put(int index, Value value) { + indexedBindings.set(index, value.freeze()); } @Override public TensorType getType(Reference reference) { - Integer index = nameToIndex().get(reference.toString()); - if (index == null) return null; - return values[index].type(); + // TODO: Support function refs + // TODO: Add type information so we do not need to evaluate to get this + return get(requireIndexOf(reference.toString())).type(); } /** Perform a slow lookup by name */ @Override public Value get(String name) { - Integer index = nameToIndex().get(name); - if (index == null) return DoubleValue.zero; - return values[index]; + return get(requireIndexOf(name)); } /** Perform a fast lookup by index */ @Override - public final Value get(int index) { - return values[index]; + public Value get(int index) { + return indexedBindings.get(index); } - /** Perform a fast lookup directly of the value as a double. This is faster than get(index).asDouble() */ @Override - public final double getDouble(int index) { - double value = doubleValues()[index]; + public double getDouble(int index) { + double value = get(index).asDouble(); if (value == Double.NaN) throw new UnsupportedOperationException("Value at " + index + " has no double representation"); return value; } + @Override + public int getIndex(String name) { + return requireIndexOf(name); + } + + @Override + public int size() { + return indexedBindings.names().size(); + } + + private Integer requireIndexOf(String name) { + Integer index = indexedBindings.indexOf(name); + if (index == null) + throw new IllegalArgumentException("Value '" + name + "' can not be bound in " + this); + return index; + } + + @Override + public String toString() { return "context of '" + expressionName + "'"; } + /** * Creates a clone of this context suitable for evaluating against the same ranking expression * in a different thread (i.e, name name to index map, different value set. */ + // TODO: Use copy constructor instead to preserve final public LazyArrayContext clone() { - LazyArrayContext clone = (LazyArrayContext)super.clone(); - clone.values = new Value[nameToIndex().size()]; - Arrays.fill(values, constantZero); - return clone; + try { + LazyArrayContext clone = (LazyArrayContext)super.clone(); + // TODO: Either move non-lazy value setting to array or copy all lazy functions + clone.indexedBindings = indexedBindings.clone(); + return clone; + } + catch (CloneNotSupportedException e) { + throw new RuntimeException("Programming error"); + } + } + + private static class IndexedBindings implements Cloneable { + + /** The mapping from variable name to index */ + private final ImmutableMap nameToIndex; + + /** The current values set, pre-converted to doubles */ + private Value[] values; + + IndexedBindings(RankingExpression expression, Map functions, LazyArrayContext owner) { + Set bindTargets = new LinkedHashSet<>(); + extractBindTargets(expression.getRoot(), functions, bindTargets); + + values = new Value[bindTargets.size()]; + Arrays.fill(values, DoubleValue.zero); + + int i = 0; + ImmutableMap.Builder nameToIndexBuilder = new ImmutableMap.Builder<>(); + for (String variable : bindTargets) + nameToIndexBuilder.put(variable,i++); + nameToIndex = nameToIndexBuilder.build(); + + for (Map.Entry function : functions.entrySet()) { + Integer index = nameToIndex.get(function.getKey()); + if (index != null) // Referenced in this, so bind it + values[index] = new LazyValue(function.getValue(), owner); + } + } + + private void extractBindTargets(ExpressionNode node, Map functions, Set bindTargets) { + if (isFunctionReference(node)) { + String reference = node.toString(); + bindTargets.add(reference); + extractBindTargets(functions.get(reference).getBody().getRoot(), functions, bindTargets); + } + else if (node instanceof ReferenceNode) { + if (((ReferenceNode)node).getArguments().expressions().size() > 0) + throw new UnsupportedOperationException("Can not bind " + node + + ": Array lookup is not supported with features having arguments)"); + bindTargets.add(node.toString()); + } + else if (node instanceof CompositeNode) { + CompositeNode cNode = (CompositeNode)node; + for (ExpressionNode child : cNode.children()) + extractBindTargets(child, functions, bindTargets); + } + } + + private boolean isFunctionReference(ExpressionNode node) { + if ( ! (node instanceof ReferenceNode)) return false; + + ReferenceNode reference = (ReferenceNode)node; + return reference.getName().equals("rankingExpression") && reference.getArguments().size() == 1; + } + + Value get(int index) { return values[index]; } + void set(int index, Value value) { values[index] = value; } + Set names() { return nameToIndex.keySet(); } + Integer indexOf(String name) { return nameToIndex.get(name); } + + /** + * Creates a clone of this context suitable for evaluating against the same ranking expression + * in a different thread (i.e, name name to index map, different value set. + */ + @Override + public IndexedBindings clone() { + try { + IndexedBindings clone = (IndexedBindings)super.clone(); + clone.values = new Value[nameToIndex.size()]; + // TODO: Remove clone, or fill LazyValues from functions here + return clone; + } + catch (CloneNotSupportedException e) { + throw new RuntimeException("Programming error"); + } + } + } } diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java index 5bccd526571..3407c250bcc 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java @@ -2,6 +2,7 @@ package ai.vespa.models.evaluation; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext; import com.yahoo.searchlib.rankingexpression.evaluation.Context; @@ -9,6 +10,7 @@ import com.yahoo.searchlib.rankingexpression.evaluation.Context; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -24,8 +26,7 @@ public class Model { private final ImmutableList functions; /** An instance of each usage of the above function, where variables are replaced by their bindings */ - // TODO: Separate name and instance id? - private final ImmutableList boundFunctions; + private final ImmutableMap boundFunctions; public Model(String name, Collection functions) { this(name, functions, Collections.emptyList()); @@ -34,7 +35,10 @@ public class Model { Model(String name, Collection functions, Collection boundFunctions) { this.name = name; this.functions = ImmutableList.copyOf(functions); - this.boundFunctions = ImmutableList.copyOf(boundFunctions); + ImmutableMap.Builder b = new ImmutableMap.Builder<>(); + for (ExpressionFunction function : boundFunctions) + b.put(function.getName(), function); + this.boundFunctions = b.build(); } public String name() { return name; } @@ -46,7 +50,7 @@ public class Model { ExpressionFunction requireFunction(String name) { ExpressionFunction function = function(name); if (function == null) - throw new IllegalArgumentException("No function named '" + name + " in " + this + ". Available functions: " + + throw new IllegalArgumentException("No function named '" + name + "' in " + this + ". Available functions: " + functions.stream().map(f -> f.getName()).collect(Collectors.joining(", "))); return function; } @@ -60,29 +64,17 @@ public class Model { return null; } - /** Returns an immutable list of the bound function instances of this */ - List boundFunctions() { return boundFunctions; } - - /** Returns the function withe the given name, or null if none */ // TODO: Parameter overloading? - ExpressionFunction boundFunction(String name) { - for (ExpressionFunction function : boundFunctions) - if (function.getName().equals(name)) - return function; - return null; - } + /** Returns an immutable map of the bound function instances of this, indexed by the bound instance if */ + Map boundFunctions() { return boundFunctions; } /** * Returns a function which can be used to evaluate the given function * * @throws IllegalArgumentException if the function is not present */ + // TODO: Rename to singleThreadedContextFor, move context protottype creation to construction, clone here public Context contextFor(String function) { - Context context = new LazyArrayContext(requireFunction(function).getBody(), boundFunctions); - for (ExpressionFunction boundFunction : boundFunctions) { - System.out.println("Binding " + boundFunction.getName()); - context.put(boundFunction.getName(), new LazyValue(boundFunction, context)); - } - return context; + return new LazyArrayContext(requireFunction(function).getBody(), boundFunctions); } @Override diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java index 15d3ab4bf1f..7a593ba1f0d 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java @@ -4,6 +4,7 @@ package ai.vespa.models.evaluation; import ai.vespa.models.evaluation.Model; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; import com.yahoo.searchlib.rankingexpression.RankingExpression; +import com.yahoo.searchlib.rankingexpression.parser.ParseException; import com.yahoo.vespa.config.search.RankProfilesConfig; import java.util.ArrayList; @@ -29,15 +30,20 @@ class RankProfilesConfigImporter { * The map is modifiable and owned by the caller. */ public Map importFrom(RankProfilesConfig config) { - Map models = new HashMap<>(); - for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { - Model model = importProfile(profile); - models.put(model.name(), model); + try { + Map models = new HashMap<>(); + for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { + Model model = importProfile(profile); + models.put(model.name(), model); + } + return models; + } + catch (ParseException e) { + throw new IllegalArgumentException("Could not read rank profiles config - version mismatch?", e); } - return models; } - private Model importProfile(RankProfilesConfig.Rankprofile profile) { + private Model importProfile(RankProfilesConfig.Rankprofile profile) throws ParseException { List functions = new ArrayList<>(); List boundFunctions = new ArrayList<>(); ExpressionFunction firstPhase = null; @@ -48,18 +54,21 @@ class RankProfilesConfigImporter { String name = expressionMatcher.group(1); String instance = expressionMatcher.group(2); List arguments = new ArrayList<>(); // TODO: Arguments? - RankingExpression expression = RankingExpression.from(property.value()); + RankingExpression expression = new RankingExpression(name, property.value()); - if (instance == null) - functions.add(new ExpressionFunction(name, arguments, expression)); - else - boundFunctions.add(new ExpressionFunction(name + instance, arguments, expression)); + if (instance == null) // free function; use configured name + functions.add(new ExpressionFunction(name, arguments, expression)); // Use the configured name + else // Referred, bound function - use full name used in references + boundFunctions.add(new ExpressionFunction("rankingExpression(" + name + instance + ")", + arguments, expression)); } else if (property.name().equals("vespa.rank.firstphase")) { // Include in addition to macros - firstPhase = new ExpressionFunction("firstphase", new ArrayList<>(), RankingExpression.from(property.value())); + firstPhase = new ExpressionFunction("firstphase", new ArrayList<>(), + new RankingExpression("first-phase", property.value())); } else if (property.name().equals("vespa.rank.secondphase")) { // Include in addition to macros - secondPhase = new ExpressionFunction("secondphase", new ArrayList<>(), RankingExpression.from(property.value())); + secondPhase = new ExpressionFunction("secondphase", new ArrayList<>(), + new RankingExpression("second-phase", property.value())); } } if (functionByName("firstphase", functions) == null && firstPhase != null) // may be already included, depending on body diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java index 9965a3c86ba..c2f299824c8 100644 --- a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java @@ -69,7 +69,7 @@ public class ModelsEvaluatorTest { ModelsEvaluator evaluator = createEvaluator(); Context context = evaluator.contextFor("macros", "secondphase"); context.put("match", 3); - context.put("rankboost", 5); + context.put("rankBoost", 5); assertEquals(32.0, evaluator.evaluate("macros", "secondphase", context).asDouble(), delta); } diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java index 977420c1352..8babbaf90cb 100644 --- a/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java @@ -39,21 +39,22 @@ public class RankProfilesImporterTest { "30 * pow(0 - fieldMatch(description).earliness,2)", macros); assertEquals(1, macros.boundFunctions().size()); - assertBoundFunction("fourtimessum@5cf279212355b980.67f1e87166cfef86", "4 * (match + rankBoost)", macros); + assertBoundFunction("rankingExpression(fourtimessum@5cf279212355b980.67f1e87166cfef86)", + "4 * (match + rankBoost)", macros); } private void assertFunction(String name, String expression, Model model) { ExpressionFunction function = model.function(name); assertNotNull(function); assertEquals(name, function.getName()); - assertEquals(expression, function.getBody().toString()); + assertEquals(expression, function.getBody().getRoot().toString()); } private void assertBoundFunction(String name, String expression, Model model) { - ExpressionFunction function = model.boundFunction(name); - assertNotNull(function); + ExpressionFunction function = model.boundFunctions().get(name); + assertNotNull("Function '" + name + "' is present", function); assertEquals(name, function.getName()); - assertEquals(expression, function.getBody().toString()); + assertEquals(expression, function.getBody().getRoot().toString()); } } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java index aec5b2367bf..3adae63c803 100755 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java @@ -276,7 +276,7 @@ public class RankingExpression implements Serializable { * Returns the value of evaluating this expression over the given context. * * @param context The variable bindings to use for this evaluation. - * @return The evaluation result. + * @return the evaluation result. * @throws IllegalArgumentException if there are variables which are not bound in the given map */ public Value evaluate(Context context) { diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java index 893e31c7087..41bf827748a 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java @@ -54,6 +54,7 @@ public abstract class AbstractArrayContext extends Context implements Cloneable, protected final double[] doubleValues() { return indexedBindings.doubleValues(); } protected final boolean ignoreUnknownValues() { return ignoreUnknownValues; } + @Override public Set names() { return indexedBindings.names(); } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ContextIndex.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ContextIndex.java index 4f1465cd1f5..ad6facbf0af 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ContextIndex.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ContextIndex.java @@ -1,7 +1,9 @@ package com.yahoo.searchlib.rankingexpression.evaluation; /** - * Indexed context lookup methods + * Indexed context lookup methods. + * Any context which implements these methods supports optimizations where map lookups + * are replaced by indexed lookups. * * @author bratseth */ @@ -17,4 +19,8 @@ public interface ContextIndex { */ int getIndex(String name); + Value get(int index); + + double getDouble(int index); + } -- cgit v1.2.3 From e6a8a79025abd0dc4c29d74a4f22687c93f19532 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Fri, 20 Jul 2018 15:17:37 +0200 Subject: Reuse context prototypes --- .../testutil/HandlersConfigurerTestWrapper.java | 6 +- .../vespa/models/evaluation/LazyArrayContext.java | 69 +++++++++++----------- .../java/ai/vespa/models/evaluation/LazyValue.java | 4 ++ .../java/ai/vespa/models/evaluation/Model.java | 41 +++++++++---- .../vespa/models/evaluation/ModelsEvaluator.java | 3 + .../evaluation/RankProfilesConfigImporter.java | 24 +++++--- .../evaluation/RankProfilesImporterTest.java | 2 +- .../rankingexpression/ExpressionFunction.java | 2 +- 8 files changed, 91 insertions(+), 60 deletions(-) diff --git a/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java b/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java index afbf163500f..46db2cd8146 100644 --- a/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java +++ b/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java @@ -111,11 +111,7 @@ public class HandlersConfigurerTestWrapper { public void reloadConfig() { configurer.reloadConfig(++lastGeneration); - try { - configurer.getNewComponentGraph(Guice.createInjector(), false); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + configurer.getNewComponentGraph(Guice.createInjector(), false); } public void shutdown() { diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java index 609010b22af..09da165c170 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java @@ -29,7 +29,12 @@ import java.util.Set; final class LazyArrayContext extends Context implements ContextIndex { private final String expressionName; - private IndexedBindings indexedBindings; + private final IndexedBindings indexedBindings; + + private LazyArrayContext(String expressionName, IndexedBindings indexedBindings) { + this.expressionName = expressionName; + this.indexedBindings = indexedBindings.copy(this); + } /** * Create a fast lookup, lazy context for an expression. @@ -68,7 +73,6 @@ final class LazyArrayContext extends Context implements ContextIndex { @Override public TensorType getType(Reference reference) { - // TODO: Support function refs // TODO: Add type information so we do not need to evaluate to get this return get(requireIndexOf(reference.toString())).type(); } @@ -103,6 +107,9 @@ final class LazyArrayContext extends Context implements ContextIndex { return indexedBindings.names().size(); } + @Override + public Set names() { return indexedBindings.names(); } + private Integer requireIndexOf(String name) { Integer index = indexedBindings.indexOf(name); if (index == null) @@ -115,28 +122,24 @@ final class LazyArrayContext extends Context implements ContextIndex { /** * Creates a clone of this context suitable for evaluating against the same ranking expression - * in a different thread (i.e, name name to index map, different value set. + * in a different thread or for re-binding free variables. */ - // TODO: Use copy constructor instead to preserve final - public LazyArrayContext clone() { - try { - LazyArrayContext clone = (LazyArrayContext)super.clone(); - // TODO: Either move non-lazy value setting to array or copy all lazy functions - clone.indexedBindings = indexedBindings.clone(); - return clone; - } - catch (CloneNotSupportedException e) { - throw new RuntimeException("Programming error"); - } + LazyArrayContext copy() { + return new LazyArrayContext(expressionName, indexedBindings); } - private static class IndexedBindings implements Cloneable { + private static class IndexedBindings { /** The mapping from variable name to index */ private final ImmutableMap nameToIndex; /** The current values set, pre-converted to doubles */ - private Value[] values; + private final Value[] values; + + private IndexedBindings(ImmutableMap nameToIndex, Value[] values) { + this.nameToIndex = nameToIndex; + this.values = values; + } IndexedBindings(RankingExpression expression, Map functions, LazyArrayContext owner) { Set bindTargets = new LinkedHashSet<>(); @@ -162,12 +165,13 @@ final class LazyArrayContext extends Context implements ContextIndex { if (isFunctionReference(node)) { String reference = node.toString(); bindTargets.add(reference); + extractBindTargets(functions.get(reference).getBody().getRoot(), functions, bindTargets); } + else if (isConstant(node)) { + // Ignore + } else if (node instanceof ReferenceNode) { - if (((ReferenceNode)node).getArguments().expressions().size() > 0) - throw new UnsupportedOperationException("Can not bind " + node + - ": Array lookup is not supported with features having arguments)"); bindTargets.add(node.toString()); } else if (node instanceof CompositeNode) { @@ -184,26 +188,23 @@ final class LazyArrayContext extends Context implements ContextIndex { return reference.getName().equals("rankingExpression") && reference.getArguments().size() == 1; } + private boolean isConstant(ExpressionNode node) { + if ( ! (node instanceof ReferenceNode)) return false; + + ReferenceNode reference = (ReferenceNode)node; + return reference.getName().equals("value") && reference.getArguments().size() == 1; + } + Value get(int index) { return values[index]; } void set(int index, Value value) { values[index] = value; } Set names() { return nameToIndex.keySet(); } Integer indexOf(String name) { return nameToIndex.get(name); } - /** - * Creates a clone of this context suitable for evaluating against the same ranking expression - * in a different thread (i.e, name name to index map, different value set. - */ - @Override - public IndexedBindings clone() { - try { - IndexedBindings clone = (IndexedBindings)super.clone(); - clone.values = new Value[nameToIndex.size()]; - // TODO: Remove clone, or fill LazyValues from functions here - return clone; - } - catch (CloneNotSupportedException e) { - throw new RuntimeException("Programming error"); - } + IndexedBindings copy(Context context) { + Value[] valueCopy = new Value[values.length]; + for (int i = 0; i < values.length; i++) + valueCopy[i] = values[i] instanceof LazyValue ? ((LazyValue)values[i]).copyFor(context) : values[i]; + return new IndexedBindings(nameToIndex, valueCopy); } } diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyValue.java b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyValue.java index b026007346d..7e21e426962 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyValue.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyValue.java @@ -143,4 +143,8 @@ class LazyValue extends Value { return computedValue().hashCode(); } + LazyValue copyFor(Context context) { + return new LazyValue(this.function, context); + } + } diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java index 3407c250bcc..766e17a7320 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java @@ -4,7 +4,6 @@ package ai.vespa.models.evaluation; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; -import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext; import com.yahoo.searchlib.rankingexpression.evaluation.Context; import java.util.Collection; @@ -25,20 +24,34 @@ public class Model { /** Free functions */ private final ImmutableList functions; - /** An instance of each usage of the above function, where variables are replaced by their bindings */ - private final ImmutableMap boundFunctions; + /** Instances of each usage of the above function, where variables (if any) are replaced by their bindings */ + private final ImmutableMap referredFunctions; + + private final ImmutableMap contextPrototypes; public Model(String name, Collection functions) { this(name, functions, Collections.emptyList()); } - Model(String name, Collection functions, Collection boundFunctions) { + Model(String name, Collection functions, Collection referredFunctions) { this.name = name; this.functions = ImmutableList.copyOf(functions); - ImmutableMap.Builder b = new ImmutableMap.Builder<>(); - for (ExpressionFunction function : boundFunctions) - b.put(function.getName(), function); - this.boundFunctions = b.build(); + + ImmutableMap.Builder functionsBuilder = new ImmutableMap.Builder<>(); + for (ExpressionFunction function : referredFunctions) + functionsBuilder.put(function.getName(), function); + this.referredFunctions = functionsBuilder.build(); + + ImmutableMap.Builder contextBuilder = new ImmutableMap.Builder<>(); + for (ExpressionFunction function : functions) { + try { + contextBuilder.put(function.getName(), new LazyArrayContext(function.getBody(), this.referredFunctions)); + } + catch (RuntimeException e) { + throw new IllegalArgumentException("Could not prepare an evaluation context for " + function, e); + } + } + this.contextPrototypes = contextBuilder.build(); } public String name() { return name; } @@ -55,6 +68,14 @@ public class Model { return function; } + /** Returns the given function, or throws a IllegalArgumentException if it does not exist */ + private LazyArrayContext requireContextProprotype(String name) { + LazyArrayContext context = contextPrototypes.get(name); + if (context == null) // Implies function is not present + throw new IllegalArgumentException("No function named '" + name + "' in " + this + ". Available functions: " + + functions.stream().map(f -> f.getName()).collect(Collectors.joining(", "))); + return context; + } /** Returns the function withe the given name, or null if none */ // TODO: Parameter overloading? ExpressionFunction function(String name) { @@ -65,7 +86,7 @@ public class Model { } /** Returns an immutable map of the bound function instances of this, indexed by the bound instance if */ - Map boundFunctions() { return boundFunctions; } + Map boundFunctions() { return referredFunctions; } /** * Returns a function which can be used to evaluate the given function @@ -74,7 +95,7 @@ public class Model { */ // TODO: Rename to singleThreadedContextFor, move context protottype creation to construction, clone here public Context contextFor(String function) { - return new LazyArrayContext(requireFunction(function).getBody(), boundFunctions); + return requireContextProprotype(function).copy(); } @Override diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java index 35c6a269edd..44fec1b9f2c 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java @@ -2,7 +2,10 @@ package ai.vespa.models.evaluation; import com.google.common.collect.ImmutableMap; +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.RankingExpression; import com.yahoo.searchlib.rankingexpression.evaluation.Context; +import com.yahoo.searchlib.rankingexpression.evaluation.Value; import com.yahoo.tensor.Tensor; import com.yahoo.vespa.config.search.RankProfilesConfig; diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java index 7a593ba1f0d..5d6a7b0c942 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java @@ -21,15 +21,14 @@ import java.util.regex.Pattern; */ class RankProfilesConfigImporter { - // TODO: Move to separate class ... or something - static final Pattern expressionPattern = + private static final Pattern expressionPattern = Pattern.compile("rankingExpression\\(([a-zA-Z0-9_]+)(@[a-f0-9]+\\.[a-f0-9]+)?\\)\\.rankingScript"); /** * Returns a map of the models contained in this config, indexed on name. * The map is modifiable and owned by the caller. */ - public Map importFrom(RankProfilesConfig config) { + Map importFrom(RankProfilesConfig config) { try { Map models = new HashMap<>(); for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { @@ -56,11 +55,12 @@ class RankProfilesConfigImporter { List arguments = new ArrayList<>(); // TODO: Arguments? RankingExpression expression = new RankingExpression(name, property.value()); - if (instance == null) // free function; use configured name - functions.add(new ExpressionFunction(name, arguments, expression)); // Use the configured name - else // Referred, bound function - use full name used in references - boundFunctions.add(new ExpressionFunction("rankingExpression(" + name + instance + ")", - arguments, expression)); + if (instance == null) // free function; make available in model under configured name + functions.add(new ExpressionFunction(name, arguments, expression)); // + + // Make all functions, bound or not available under the name they are referenced by in expressions + boundFunctions.add(new ExpressionFunction("rankingExpression(" + name + (instance != null ? instance : "") + ")", + arguments, expression)); } else if (property.name().equals("vespa.rank.firstphase")) { // Include in addition to macros firstPhase = new ExpressionFunction("firstphase", new ArrayList<>(), @@ -75,7 +75,13 @@ class RankProfilesConfigImporter { functions.add(firstPhase); if (functionByName("secondphase", functions) == null && secondPhase != null) // may be already included, depending on body functions.add(secondPhase); - return new Model(profile.name(), functions, boundFunctions); + + try { + return new Model(profile.name(), functions, boundFunctions); + } + catch (RuntimeException e) { + throw new IllegalArgumentException("Could not load model '" + profile.name() + "'", e); + } } private ExpressionFunction functionByName(String name, List functions) { diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java index 8babbaf90cb..c520a389b15 100644 --- a/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java @@ -38,7 +38,7 @@ public class RankProfilesImporterTest { "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + " + "30 * pow(0 - fieldMatch(description).earliness,2)", macros); - assertEquals(1, macros.boundFunctions().size()); + assertEquals(4, macros.boundFunctions().size()); assertBoundFunction("rankingExpression(fourtimessum@5cf279212355b980.67f1e87166cfef86)", "4 * (match + rankBoost)", macros); } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/ExpressionFunction.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/ExpressionFunction.java index a3ab21a5966..5a3773bfa8b 100755 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/ExpressionFunction.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/ExpressionFunction.java @@ -110,7 +110,7 @@ public class ExpressionFunction { @Override public String toString() { - return name; + return "function '" + name + "'"; } /** -- cgit v1.2.3 From 43d5255f1879214796482deea5a5c024a8abf618 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Fri, 20 Jul 2018 16:09:21 +0200 Subject: Tighten interface --- .../vespa/models/evaluation/FunctionEvaluator.java | 54 ++++++++++++++++++++ .../vespa/models/evaluation/LazyArrayContext.java | 17 ++++--- .../java/ai/vespa/models/evaluation/Model.java | 14 +++--- .../vespa/models/evaluation/ModelsEvaluator.java | 27 ++-------- .../evaluation/RankProfilesConfigImporter.java | 1 - .../models/evaluation/ModelsEvaluatorTest.java | 57 +++++----------------- 6 files changed, 89 insertions(+), 81 deletions(-) create mode 100644 model-inference/src/main/java/ai/vespa/models/evaluation/FunctionEvaluator.java diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/FunctionEvaluator.java b/model-inference/src/main/java/ai/vespa/models/evaluation/FunctionEvaluator.java new file mode 100644 index 00000000000..c4ecc1b25c9 --- /dev/null +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/FunctionEvaluator.java @@ -0,0 +1,54 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.yahoo.searchlib.rankingexpression.evaluation.TensorValue; +import com.yahoo.tensor.Tensor; +import com.yahoo.tensor.TensorType; + +/** + * An evaluator which can be used to evaluate a single function once. + * + * @author bratseth + */ +// This wraps all access to the context and the ranking expression to avoid incorrect usage +public class FunctionEvaluator { + + private final LazyArrayContext context; + private boolean evaluated = false; + + FunctionEvaluator(LazyArrayContext context) { + this.context = context; + } + + /** + * Binds the given variable referred in this expression to the given value. + * + * @param name the variable to bind + * @param value the value this becomes bound to + * @return this for chaining + */ + public FunctionEvaluator bind(String name, Tensor value) { + if (evaluated) + throw new IllegalStateException("You cannot bind a value in a used evaluator"); + context.put(name, new TensorValue(value)); + return this; + } + + /** + * Binds the given variable referred in this expression to the given value. + * This is equivalent to bind(name, Tensor.Builder.of(TensorType.empty).cell(value).build()) + * + * @param name the variable to bind + * @param value the value this becomes bound to + * @return this for chaining + */ + public FunctionEvaluator bind(String name, double value) { + return bind(name, Tensor.Builder.of(TensorType.empty).cell(value).build()); + } + + public Tensor evaluate() { + evaluated = true; + return context.expression().evaluate(context).asTensor(); + } + +} diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java index 09da165c170..729d8af01dc 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java @@ -17,7 +17,6 @@ import com.yahoo.tensor.TensorType; import java.util.Arrays; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -28,11 +27,11 @@ import java.util.Set; */ final class LazyArrayContext extends Context implements ContextIndex { - private final String expressionName; + private final RankingExpression expression; private final IndexedBindings indexedBindings; - private LazyArrayContext(String expressionName, IndexedBindings indexedBindings) { - this.expressionName = expressionName; + private LazyArrayContext(RankingExpression expression, IndexedBindings indexedBindings) { + this.expression = expression; this.indexedBindings = indexedBindings.copy(this); } @@ -42,7 +41,7 @@ final class LazyArrayContext extends Context implements ContextIndex { * @param expression the expression to create a context for */ LazyArrayContext(RankingExpression expression, Map functions) { - this.expressionName = expression.getName(); + this.expression = expression; this.indexedBindings = new IndexedBindings(expression, functions, this); } @@ -118,14 +117,16 @@ final class LazyArrayContext extends Context implements ContextIndex { } @Override - public String toString() { return "context of '" + expressionName + "'"; } + public String toString() { return "context of '" + expression.getName() + "'"; } + + RankingExpression expression() { return expression; } /** - * Creates a clone of this context suitable for evaluating against the same ranking expression + * Creates a copy of this context suitable for evaluating against the same ranking expression * in a different thread or for re-binding free variables. */ LazyArrayContext copy() { - return new LazyArrayContext(expressionName, indexedBindings); + return new LazyArrayContext(expression, indexedBindings); } private static class IndexedBindings { diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java index 766e17a7320..9a639d0803f 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/Model.java @@ -4,7 +4,6 @@ package ai.vespa.models.evaluation; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; -import com.yahoo.searchlib.rankingexpression.evaluation.Context; import java.util.Collection; import java.util.Collections; @@ -34,6 +33,7 @@ public class Model { } Model(String name, Collection functions, Collection referredFunctions) { + // TODO: Optimize functions this.name = name; this.functions = ImmutableList.copyOf(functions); @@ -56,7 +56,7 @@ public class Model { public String name() { return name; } - /** Returns an immutable list of the free (callable) functions of this */ + /** Returns an immutable list of the free functions of this */ public List functions() { return functions; } /** Returns the given function, or throws a IllegalArgumentException if it does not exist */ @@ -89,13 +89,15 @@ public class Model { Map boundFunctions() { return referredFunctions; } /** - * Returns a function which can be used to evaluate the given function + * Returns an evaluator which can be used to evaluate the given function in a single thread once. + + * Usage: + * Tensor result = model.evaluatorOf("myFunction").bind("foo", value).bind("bar", value).evaluate() * * @throws IllegalArgumentException if the function is not present */ - // TODO: Rename to singleThreadedContextFor, move context protottype creation to construction, clone here - public Context contextFor(String function) { - return requireContextProprotype(function).copy(); + public FunctionEvaluator evaluatorOf(String function) { // TODO: Parameter overloading? + return new FunctionEvaluator(requireContextProprotype(function).copy()); } @Override diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java index 44fec1b9f2c..b36e06e5505 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java @@ -2,11 +2,6 @@ package ai.vespa.models.evaluation; import com.google.common.collect.ImmutableMap; -import com.yahoo.searchlib.rankingexpression.ExpressionFunction; -import com.yahoo.searchlib.rankingexpression.RankingExpression; -import com.yahoo.searchlib.rankingexpression.evaluation.Context; -import com.yahoo.searchlib.rankingexpression.evaluation.Value; -import com.yahoo.tensor.Tensor; import com.yahoo.vespa.config.search.RankProfilesConfig; import java.util.Map; @@ -14,7 +9,8 @@ import java.util.stream.Collectors; /** * Evaluates machine-learned models added to Vespa applications and available as config form. - * TODO: Write JavaDoc similar to RankingExpression + * Usage: + * Tensor result = evaluator.bind("foo", value).bind("bar", value").evaluate() * * @author bratseth */ @@ -34,25 +30,12 @@ public class ModelsEvaluator { * * @throws IllegalArgumentException if the function or model is not present */ - public Context contextFor(String modelName, String functionName) { - return requireModel(modelName).contextFor(functionName); - } - - /** - * Evaluates the given function in the given model. - * - * @param modelName the name of the model to evaluate - * @param functionName the function to evaluate in the model - * @param context the evaluation context which provides bindings of the arguments to the function - * @return the tensor resulting from evaluating the model, never null - * @throws IllegalArgumentException if the model of function is not present - */ - public Tensor evaluate(String modelName, String functionName, Context context) { - return requireModel(modelName).requireFunction(functionName).getBody().evaluate(context).asTensor(); + public FunctionEvaluator evaluatorOf(String modelName, String functionName) { + return requireModel(modelName).evaluatorOf(functionName); } /** Returns the given model, or throws a IllegalArgumentException if it does not exist */ - public Model requireModel(String name) { + Model requireModel(String name) { Model model = models.get(name); if (model == null) throw new IllegalArgumentException("No model named '" + name + ". Available models: " + diff --git a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java index 5d6a7b0c942..bd0453f2826 100644 --- a/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java +++ b/model-inference/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java @@ -1,7 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.models.evaluation; -import ai.vespa.models.evaluation.Model; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; import com.yahoo.searchlib.rankingexpression.RankingExpression; import com.yahoo.searchlib.rankingexpression.parser.ParseException; diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java index c2f299824c8..38a5a0c9797 100644 --- a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java @@ -3,10 +3,6 @@ package ai.vespa.models.evaluation; import com.yahoo.config.subscription.ConfigGetter; import com.yahoo.config.subscription.FileSource; -import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext; -import com.yahoo.searchlib.rankingexpression.evaluation.Context; -import com.yahoo.searchlib.rankingexpression.evaluation.MapContext; -import com.yahoo.searchlib.rankingexpression.evaluation.Value; import com.yahoo.tensor.Tensor; import com.yahoo.vespa.config.search.RankProfilesConfig; import org.junit.Test; @@ -22,55 +18,28 @@ public class ModelsEvaluatorTest { private static final double delta = 0.00000000001; - private ModelsEvaluator createEvaluator() { + private ModelsEvaluator createModels() { String configPath = "src/test/resources/config/rankexpression/rank-profiles.cfg"; RankProfilesConfig config = new ConfigGetter<>(new FileSource(new File(configPath)), RankProfilesConfig.class).getConfig(""); return new ModelsEvaluator(config); } @Test - public void testScalarMapContextEvaluation() { - ModelsEvaluator evaluator = createEvaluator(); - MapContext context = new MapContext(); - context.put("var1", 3); - context.put("var2", 5); - assertEquals(32.0, evaluator.evaluate("macros", "fourtimessum", context).asDouble(), delta); + public void testTensorEvaluation() { + ModelsEvaluator models = createModels(); + FunctionEvaluator function = models.evaluatorOf("macros", "fourtimessum"); + function.bind("var1", Tensor.from("{{x:0}:3,{x:1}:5}")); + function.bind("var2", Tensor.from("{{x:0}:7,{x:1}:11}")); + assertEquals(Tensor.from("{{x:0}:40.0,{x:1}:64.0}"), function.evaluate()); } @Test - public void testTensorMapContextEvaluation() { - ModelsEvaluator evaluator = createEvaluator(); - MapContext context = new MapContext(); - context.put("var1", Value.of(Tensor.from("{{x:0}:3,{x:1}:5}"))); - context.put("var2", Value.of(Tensor.from("{{x:0}:7,{x:1}:11}"))); - assertEquals(Tensor.from("{{x:0}:40.0,{x:1}:64.0}"), evaluator.evaluate("macros", "fourtimessum", context)); - } - - @Test - public void testScalarArrayContextEvaluation() { - ModelsEvaluator evaluator = createEvaluator(); - ArrayContext context = new ArrayContext(evaluator.requireModel("macros").requireFunction("fourtimessum").getBody()); - context.put("var1", Value.of(Tensor.from("{{x:0}:3,{x:1}:5}"))); - context.put("var2", Value.of(Tensor.from("{{x:0}:7,{x:1}:11}"))); - assertEquals(Tensor.from("{{x:0}:40.0,{x:1}:64.0}"), evaluator.evaluate("macros", "fourtimessum", context)); - } - - @Test - public void testTensorArrayContextEvaluation() { - ModelsEvaluator evaluator = createEvaluator(); - ArrayContext context = new ArrayContext(evaluator.requireModel("macros").requireFunction("fourtimessum").getBody()); - context.put("var1", Value.of(Tensor.from("{{x:0}:3,{x:1}:5}"))); - context.put("var2", Value.of(Tensor.from("{{x:0}:7,{x:1}:11}"))); - assertEquals(Tensor.from("{{x:0}:40.0,{x:1}:64.0}"), evaluator.evaluate("macros", "fourtimessum", context)); - } - - @Test - public void testEvaluationDependingOnBoundMacro() { - ModelsEvaluator evaluator = createEvaluator(); - Context context = evaluator.contextFor("macros", "secondphase"); - context.put("match", 3); - context.put("rankBoost", 5); - assertEquals(32.0, evaluator.evaluate("macros", "secondphase", context).asDouble(), delta); + public void testEvaluationDependingOnMacroTakingArguments() { + ModelsEvaluator models = createModels(); + FunctionEvaluator function = models.evaluatorOf("macros", "secondphase"); + function.bind("match", 3); + function.bind("rankBoost", 5); + assertEquals(32.0, function.evaluate().asDouble(), delta); } } -- cgit v1.2.3 From 36805c46674077714af3e076d2a35868534c5b04 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Fri, 20 Jul 2018 16:19:58 +0200 Subject: Add TODOs --- .../test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java index 38a5a0c9797..f2031b140e7 100644 --- a/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java +++ b/model-inference/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java @@ -42,4 +42,10 @@ public class ModelsEvaluatorTest { assertEquals(32.0, function.evaluate().asDouble(), delta); } + // TODO: Test argument-less function + // TODO: Test that binding nonexisting variable doesn't work + // TODO: Test that rebinding dopesn't work + // TODO: Test with nested macros + // TODO: Test TF/ONNX model + } -- cgit v1.2.3