Skip to content

Commit ec01933

Browse files
committed
Support for priority levels of kernels in the catalog.
- Situation: - So far, the DAPHNE compiler selected the kernel for a DaphneIR operation from the kernel catalog based on argument/result types and backend. - (Besides that, the user can specify a kernel hint in DaphneDSL, which will be respected by the compiler.) - In case there are multiple applicable kernels, the DAPHNE compiler simply used the first one it finds. - This commit introduces a priority to kernels registered in the kernel catalog. - The priority is a signed integer (0 by default). - If multiple kernels are applicable, the DAPHNE compiler chooses the one with the highest priority (e.g., a priority of 1 is better than 0); if there are multiple kernels with the highest priority, any of them is used. - All built-in kernels (those mentioned in kernels.json) are registered with the default priority of 0. - When registering custom kernel extensions, the user can optionally specify a priority on the command line. - A meaningful use of this feature is to make the DAPHNE compiler use the kernels provided by a custom extension whenever they are applicable (prefer them over the built-in kernels). - So far, the alternative would have been using manual kernel hints in DaphneDSL, but this approch can be cumbersome/error-prone and is not possible when the custom kernel implements a DaphneIR operation that has to direct counterpart in DaphneDSL. - Extended the test cases for kernel extensions. - Updated the documentation on extending DAPHNE (also added it to the navigation, where it was missing so far). - Other noteworthy changes: - Errors happening during the population of the kernel catalog at DAPHNE start-up result in a parser error now (with the respective status code).
1 parent 40e24a2 commit ec01933

File tree

11 files changed

+169
-27
lines changed

11 files changed

+169
-27
lines changed

doc/Extensions.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,12 @@ make
173173
### Step 3: Using a Kernel Extension
174174

175175
The kernels in a kernel extension can be used either automatically by DAPHNE or manually by the user.
176-
Automatic use is currently restricted to the selection of the kernel based on result/argument data/value types, but in the future we plan to support custom cost models as well.
177-
Besides that, the manual employment of custom kernels is very useful for experimentation, e.g., to see the impact of the kernel in the context of a larger integrated data analysis pipeline.
178-
To this end, DaphneDSL [compiler hints](/doc/DaphneDSL/LanguageRef.md#compiler-hints) tell DAPHNE to use a specific kernel, even though DAPHNE's optimizing compiler may not choose the kernel, otherwise.
176+
The manual use has precedence over the automatic use.
177+
178+
#### Manual Use of Custom Kernels
179+
180+
The manual employment of custom kernels is very useful for experimentation, e.g., to see the impact of a particular kernel at a certain point of a larger integrated data analysis pipeline.
181+
To this end, DaphneDSL [compiler hints](/doc/DaphneDSL/LanguageRef.md#compiler-hints) tell DAPHNE to use a specific kernel in a specific place, even though DAPHNE's optimizing compiler may not choose the kernel, otherwise.
179182

180183
*Running example:*
181184

@@ -205,7 +208,7 @@ s = sum::mySumSeq(X);
205208
print(s);
206209
```
207210

208-
We execute this script with the following command, whereby the argument `--kernel-ext` specified the kernel catalog JSON file of the extension to use:
211+
We execute this script with the following command, whereby the argument `--kernel-ext` specifies the kernel catalog JSON file of the extension to use:
209212
```bash
210213
bin/daphne --kernel-ext scripts/examples/extensions/myKernels/myKernels.json scripts/examples/extensions/myKernels/demoSeq.daphne
211214
```
@@ -222,4 +225,27 @@ print(s);
222225
We execute this script by:
223226
```bash
224227
bin/daphne --kernel-ext scripts/examples/extensions/myKernels/myKernels.json scripts/examples/extensions/myKernels/demoSIMD.daphne
228+
```
229+
230+
#### Automatic Use of Custom Kernels
231+
232+
The automatic use of custom kernels is currently restricted to the selection of a kernel based on its result/argument data/value types and its priority level.
233+
In the future we plan to support custom cost models as well.
234+
235+
*Running example:*
236+
237+
Continuing the running example from above, we can make DAPHNE use the custom kernels `mySumSeq()` or `mySumSIMD()` even without a manual kernel hint by specifying a suitable *priority* when registering the `myKernels` extension with DAPHNE.
238+
239+
Priority levels can optionally be specified with the `--kernel-ext` command line argument by appending a colon (`:`) followed by the priority as an integer.
240+
The default priority of `0` is used for all built-in kernels and for extension kernels in case no priority is specified.
241+
When registering a kernel extension, the given priority is assigned to *all* kernels provided by the extension.
242+
When multiple kernels are applicable for an operation based on the combination of argument/result data/value types as well as the backend, DAPHNE chooses the kernel with the highest priority.
243+
If there are multiple kernels with the highest priority, it is not specified which of them is used.
244+
245+
By registering a kernel extension with a priority greater than zero, one can enforce that the kernels provided by the extension are always preferred over the built-in ones whenever they are applicable.
246+
For instance, the following command registers the `myKernels` extension with a priority of `1`.
247+
As the `myKernels` extension provides two kernels for the same operation, argument/result types, and backend, we cannot tell, based on priorities, which of these kernels will be used, but we can be sure that the built-in kernel will not be employed.
248+
249+
```bash
250+
bin/daphne --kernel-ext scripts/examples/extensions/myKernels/myKernels.json:1 scripts/examples/extensions/myKernels/demo.daphne
225251
```

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ nav:
6565
- FPGAconfiguration.md
6666
- MPI-Usage.md
6767
- Profiling.md
68+
- 'Custom Extensions': Extensions.md
6869
- 'Developers':
6970
- development/Contributing.md
7071
- development/BuildingDaphne.md

src/api/internal/daphne_internal.cpp

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -571,14 +571,48 @@ int startDAPHNE(int argc, const char **argv, DaphneLibResult *daphneLibRes, int
571571
// ************************************************************************
572572

573573
KernelCatalog &kc = executor.getUserConfig().kernelCatalog;
574-
// kc.dump();
575-
KernelCatalogParser kcp(mctx);
576-
kcp.parseKernelCatalog(user_config.libdir + "/catalog.json", kc);
577-
if (user_config.use_cuda)
578-
kcp.parseKernelCatalog(user_config.libdir + "/CUDAcatalog.json", kc);
579-
// kc.dump();
580-
if (!kernelExt.empty())
581-
kcp.parseKernelCatalog(kernelExt, kc);
574+
try {
575+
// kc.dump();
576+
KernelCatalogParser kcp(mctx);
577+
kcp.parseKernelCatalog(user_config.libdir + "/catalog.json", kc, 0);
578+
if (user_config.use_cuda)
579+
kcp.parseKernelCatalog(user_config.libdir + "/CUDAcatalog.json", kc, 0);
580+
// kc.dump();
581+
if (!kernelExt.empty()) {
582+
std::string extCatalogFile;
583+
int64_t extPriority;
584+
585+
const std::string prioritySep = ":";
586+
const size_t pos = kernelExt.rfind(prioritySep);
587+
if (pos != std::string::npos) { // a priority was specified for the extension
588+
extCatalogFile = kernelExt.substr(0, pos);
589+
const std::string extPriorityStr(kernelExt.substr(pos + prioritySep.size()));
590+
try {
591+
size_t idx;
592+
extPriority = std::stoll(extPriorityStr, &idx);
593+
if (idx != extPriorityStr.size())
594+
// stoll() did not consume all characters in extPriorityStr, there is some non-integer part at
595+
// the end of the string.
596+
throw std::runtime_error(""); // the error message is generated in the catch-block below
597+
} catch (std::exception &e) {
598+
throw std::runtime_error("invalid priority for kernel extension, expected an integer after the '" +
599+
prioritySep + "', but found '" + extPriorityStr + "': '" + kernelExt +
600+
"'");
601+
}
602+
} else { // no priority was specified for the extension
603+
extCatalogFile = kernelExt;
604+
extPriority = 0;
605+
}
606+
607+
kcp.parseKernelCatalog(extCatalogFile, kc, extPriority);
608+
}
609+
} catch (std::exception &e) {
610+
logErrorDaphneLibAware(daphneLibRes, "Parser error: " + std::string(e.what()));
611+
return StatusCode::PARSER_ERROR;
612+
} catch (...) {
613+
logErrorDaphneLibAware(daphneLibRes, "Parser error: Unknown exception");
614+
return StatusCode::PARSER_ERROR;
615+
}
582616

583617
// ************************************************************************
584618
// Parse, compile and execute DaphneDSL script

src/compiler/catalog/KernelCatalog.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,18 @@ struct KernelInfo {
6060
*/
6161
const std::string libPath;
6262

63+
/**
64+
* @brief The priority of this kernel.
65+
*
66+
* If there are multiple applicable kernels, the one with the highest priority will be preferred.
67+
*/
68+
const int64_t priority;
69+
6370
KernelInfo(const std::string kernelFuncName, const std::vector<mlir::Type> resTypes,
64-
const std::vector<mlir::Type> argTypes, const std::string backend, const std::string libPath)
65-
: kernelFuncName(kernelFuncName), resTypes(resTypes), argTypes(argTypes), backend(backend), libPath(libPath) {
71+
const std::vector<mlir::Type> argTypes, const std::string backend, const std::string libPath,
72+
int64_t priority)
73+
: kernelFuncName(kernelFuncName), resTypes(resTypes), argTypes(argTypes), backend(backend), libPath(libPath),
74+
priority(priority) {
6675
//
6776
}
6877
};

src/compiler/lowering/RewriteToCallKernelOpPass.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "llvm/ADT/ArrayRef.h"
3636

3737
#include <iostream>
38+
#include <limits>
3839
#include <memory>
3940
#include <sstream>
4041
#include <stdexcept>
@@ -431,7 +432,8 @@ class KernelReplacement : public RewritePattern {
431432
const size_t numArgs = lookupArgTys.size();
432433
const size_t numRess = lookupResTys.size();
433434
int chosenKernelIdx = -1;
434-
for (size_t i = 0; i < kernelInfos.size() && chosenKernelIdx == -1; i++) {
435+
int64_t chosenKernelPriority = std::numeric_limits<int64_t>::min();
436+
for (size_t i = 0; i < kernelInfos.size(); i++) {
435437
auto ki = kernelInfos[i];
436438
if (ki.backend != backend)
437439
continue;
@@ -447,8 +449,11 @@ class KernelReplacement : public RewritePattern {
447449
for (size_t i = 0; i < numRess && !mismatch; i++)
448450
if (lookupResTys[i] != ki.resTypes[i])
449451
mismatch = true;
450-
if (!mismatch)
452+
453+
if (!mismatch && (ki.priority > chosenKernelPriority || chosenKernelIdx == -1)) {
451454
chosenKernelIdx = i;
455+
chosenKernelPriority = ki.priority;
456+
}
452457
}
453458
if (chosenKernelIdx == -1) {
454459
std::stringstream s;

src/parser/catalog/KernelCatalogParser.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ void KernelCatalogParser::mapTypes(const std::vector<std::string> &in, std::vect
109109
}
110110
}
111111

112-
void KernelCatalogParser::parseKernelCatalog(const std::string &filePath, KernelCatalog &kc) const {
112+
void KernelCatalogParser::parseKernelCatalog(const std::string &filePath, KernelCatalog &kc, int64_t priority) const {
113113
std::filesystem::path dirPath = std::filesystem::path(filePath).parent_path();
114114
try {
115115
std::ifstream kernelsConfigFile(filePath);
@@ -130,7 +130,7 @@ void KernelCatalogParser::parseKernelCatalog(const std::string &filePath, Kernel
130130
mapTypes(kernelData["resTypes"], resTypes, "result", kernelFuncName, opMnemonic, backend);
131131
std::vector<mlir::Type> argTypes;
132132
mapTypes(kernelData["argTypes"], argTypes, "argument", kernelFuncName, opMnemonic, backend);
133-
kc.registerKernel(opMnemonic, KernelInfo(kernelFuncName, resTypes, argTypes, backend, libPath));
133+
kc.registerKernel(opMnemonic, KernelInfo(kernelFuncName, resTypes, argTypes, backend, libPath, priority));
134134
}
135135
} catch (std::exception &e) {
136136
throw std::runtime_error("error while parsing kernel catalog file `" + filePath + "`: " + e.what());

src/parser/catalog/KernelCatalogParser.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class KernelCatalogParser {
6464
*
6565
* @param filePath The path to the file to extract kernel information from.
6666
* @param kc The kernel catalog to register the kernels with.
67+
* @param priority The kernel priority to use for all kernels parsed from the given file.
6768
*/
68-
void parseKernelCatalog(const std::string &filePath, KernelCatalog &kc) const;
69+
void parseKernelCatalog(const std::string &filePath, KernelCatalog &kc, int64_t priority) const;
6970
};

src/runtime/distributed/worker/WorkerImpl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ WorkerImpl::Status WorkerImpl::Compute(std::vector<WorkerImpl::StoredInfo> *outp
7575

7676
KernelCatalog &kc = executor.getUserConfig().kernelCatalog;
7777
KernelCatalogParser kcp(executor.getContext());
78-
kcp.parseKernelCatalog(cfg.libdir + "/catalog.json", kc);
78+
kcp.parseKernelCatalog(cfg.libdir + "/catalog.json", kc, 0);
7979
if (executor.getUserConfig().use_cuda)
80-
kcp.parseKernelCatalog(cfg.libdir + "/CUDAcatalog.json", kc);
80+
kcp.parseKernelCatalog(cfg.libdir + "/CUDAcatalog.json", kc, 0);
8181

8282
mlir::OwningOpRef<mlir::ModuleOp> module(mlir::parseSourceString<mlir::ModuleOp>(mlirCode, executor.getContext()));
8383
if (!module) {

test/api/cli/extensibility/ExtensionTest.cpp

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,74 @@ TEST_CASE("extension_kernel", TAG_EXTENSIBILITY) {
5252
REQUIRE(status == 0);
5353

5454
// *************************************************************************
55-
// Use the custom kernel extension.
55+
// Use the custom kernel extension based on hints or priority.
5656
// *************************************************************************
57-
// Run a DaphneDSL script which uses a kernel from the extension through a
58-
// kernel hint. The extension is registered with DAPHNE at run-time. DAPHNE
59-
// itself is not re-built or anything.
60-
compareDaphneToStr("hello from mySumAll\n2\n", std::string(dirPath + "extension_kernel_usage.daphne").c_str(),
61-
"--kernel-ext", std::string(dirPath + "kernel_extension_test/myKernels.json").c_str());
57+
// All of the following invocations of DAPHNE register the extension with DAPHNE at run-time. DAPHNE itself is not
58+
// re-built or anything. This extension provides a custom kernel for a case (DaphneIR operation, input/output types,
59+
// backend) that is already covered by an existing built-in kernel.
60+
61+
// If a kernel hint is given, this kernel must always be used, irrespective of its priority.
62+
// Run a DaphneDSL script which uses a kernel from the extension through a kernel hint.
63+
SECTION("hint, no priority") { // must use the custom kernel
64+
compareDaphneToStr("hello from mySumAll\n2\n",
65+
std::string(dirPath + "extension_kernel_usage_hint.daphne").c_str(), "--kernel-ext",
66+
std::string(dirPath + "kernel_extension_test/myKernels.json").c_str());
67+
}
68+
SECTION("hint, default priority") { // must use the custom kernel
69+
compareDaphneToStr("hello from mySumAll\n2\n",
70+
std::string(dirPath + "extension_kernel_usage_hint.daphne").c_str(), "--kernel-ext",
71+
std::string(dirPath + "kernel_extension_test/myKernels.json:0").c_str());
72+
}
73+
SECTION("hint, higher priority") { // must use the custom kernel
74+
compareDaphneToStr("hello from mySumAll\n2\n",
75+
std::string(dirPath + "extension_kernel_usage_hint.daphne").c_str(), "--kernel-ext",
76+
std::string(dirPath + "kernel_extension_test/myKernels.json:1").c_str());
77+
}
78+
SECTION("hint, lower priority") { // must use the custom kernel
79+
compareDaphneToStr("hello from mySumAll\n2\n",
80+
std::string(dirPath + "extension_kernel_usage_hint.daphne").c_str(), "--kernel-ext",
81+
std::string(dirPath + "kernel_extension_test/myKernels.json:-1").c_str());
82+
}
83+
// If no kernel hint is given, the kernel must be used when it has a higher-than-default priority and must not be
84+
// used if it has a lower-than-default priority. For "no hint, no priority" and "no hint, default priority",
85+
// DAPHNE's choice whether to use the custom or the built-in kernel is unspecified, so we don't test these cases
86+
// here.
87+
// Run a DaphneDSL script which does not use a kernel hint.
88+
SECTION("no hint, higher priority") { // must use the custom kernel
89+
compareDaphneToStr("hello from mySumAll\n2\n",
90+
std::string(dirPath + "extension_kernel_usage_nohint.daphne").c_str(), "--kernel-ext",
91+
std::string(dirPath + "kernel_extension_test/myKernels.json:1").c_str());
92+
}
93+
SECTION("no hint, lower priority") { // must NOT use the custom kernel
94+
compareDaphneToStr("2\n", std::string(dirPath + "extension_kernel_usage_nohint.daphne").c_str(), "--kernel-ext",
95+
std::string(dirPath + "kernel_extension_test/myKernels.json:-1").c_str());
96+
}
97+
// If an invalid value is specified for the priority, DAPHNE must stop, even if the kernel extension itself exists.
98+
SECTION("no hint, invalid priority (empty)") {
99+
checkDaphneStatusCode(StatusCode::PARSER_ERROR,
100+
std::string(dirPath + "extension_kernel_usage_nohint.daphne").c_str(), "--kernel-ext",
101+
std::string(dirPath + "kernel_extension_test/myKernels.json:").c_str());
102+
}
103+
SECTION("no hint, invalid priority (float)") {
104+
checkDaphneStatusCode(StatusCode::PARSER_ERROR,
105+
std::string(dirPath + "extension_kernel_usage_nohint.daphne").c_str(), "--kernel-ext",
106+
std::string(dirPath + "kernel_extension_test/myKernels.json:0.1").c_str());
107+
}
108+
SECTION("no hint, invalid priority (string)") {
109+
checkDaphneStatusCode(StatusCode::PARSER_ERROR,
110+
std::string(dirPath + "extension_kernel_usage_nohint.daphne").c_str(), "--kernel-ext",
111+
std::string(dirPath + "kernel_extension_test/myKernels.json:abc").c_str());
112+
}
113+
SECTION("no hint, invalid priority (integer and string)") {
114+
checkDaphneStatusCode(StatusCode::PARSER_ERROR,
115+
std::string(dirPath + "extension_kernel_usage_nohint.daphne").c_str(), "--kernel-ext",
116+
std::string(dirPath + "kernel_extension_test/myKernels.json:123abc").c_str());
117+
}
118+
SECTION("no hint, invalid priority (too huge integer)") {
119+
checkDaphneStatusCode(
120+
StatusCode::PARSER_ERROR, std::string(dirPath + "extension_kernel_usage_nohint.daphne").c_str(),
121+
"--kernel-ext", std::string(dirPath + "kernel_extension_test/myKernels.json:99999999999999999999").c_str());
122+
}
62123

63124
// Clear the streams.
64125
out.clear();

test/api/cli/extensibility/extension_kernel_usage.daphne renamed to test/api/cli/extensibility/extension_kernel_usage_hint.daphne

File renamed without changes.

0 commit comments

Comments
 (0)