在 VSC 中,您也可以在单一 .xclbin 和运行时环境内创建多个含不同功能的加速器。借助此类组合,您可创建一条包含两个或更多个 VSC 加速器的流水线,这些加速器以流水打拍方式对不同数据集进行操作,如 受支持的平台和启动示例 中的 sysc_compose
示例所示。以下章节中描述了两种可能的使用模型。
ACC1-CPU-ACC2 流水线
此模型定义了任务流水线,其中第一个加速器用于计算中间结果,随后由主机应用对结果加以处理,再由第二个加速器进行进一步处理。第一个加速器的输出缓冲器需经过修改(修改的同时进行复制)后再传递给第二个加速器。代码示例如下所示。
auto inBP = my_acc1::create_bufpool(vpp::input);
auto tmpoBP = my_acc1::create_bufpool(vpp::output);
auto tmpiBP = my_acc2::create_bufpool(vpp::input);
auto outBP = my_acc2::create_bufpool(vpp::output);
my_acc1::send_while(
[=]()->bool {
int* in = my_acc1::alloc_buf<int>(inBP, inSz);
int* tmp = my_acc1::alloc_buf<int>(tmpoBP, tmpSz);
my_acc1::compute(in, tmp, ...);
...;
return ...;
});
my_acc2::send_while(
[=]()->bool {
int* tmp2 = my_acc2::alloc_buf<int>(tmpiBP, tmpSz);
bool cond = my_acc1::receive_one_in_order( // or receive_one_asap
[=]() {
int* tmp1 = my_acc1::get_buf<int>(tmpoBP);
...; // tmp1 -> copy and modify -> tmp2
});
if (!cond) return false;
int* out = my_acc2::alloc_buf<int>(outBP, outSz);
my_acc2::compute(tmp2, out, ...);
...;
return true;
});
my_acc2::receive_all_in_order(
[=]() {
int* out = xfilter1::get_buf<int>(outBP);
...;
});
my_acc1::join();
my_acc2::join();
此代码在第二个加速器 my_acc2
的 send_while
循环作用域内使用专用 receive_one_in_order
API。receive_one_in_order
或 received_one asap
API 需要用户定义的 lambda 函数体,如 VPP_ACC 类 API 中所述。
在此例中,my_acc1::receive_one_in_order
(或 receive_one_asap
)将等待下一项作业按顺序(或尽快)完成,然后执行 lambda 函数体。由于 my_acc2
是从第二个加速器的 send_while
调用的,因此它会以锁步方式计算从第一个加速器 my_acc1
生成的结果。此 API 会返回布尔值,当 send_while
循环已退出且不再接收任何作业时,返回的值为 true。
ACC1-ACC2 流水线
此模式所定义的任务流水线中,第一个加速器的输出缓冲器可供第二个加速器直接使用(无需执行任何主机 CPU 同步)。如 VPP_ACC 类 API 中所述,transfer_buf()
API 会提取缓冲池对象并返回对应于正确迭代的缓冲器。通过使用此 API,即可将结果缓冲器从第一个加速器无缝传输到第二个加速器,而无需复制数据。VSC 运行时会跨各加速器和作业迭代器,自动管理此类缓冲器的复用。
代码示例如下所示:
auto inBP = my_acc1::create_bufpool(vpp::input);
auto tmpBP = my_acc1::create_bufpool(vpp::remote);
auto outBP = my_acc2::create_bufpool(vpp::output);
my_acc1::send_while(
[=]()->bool {
int* in = my_acc1::alloc_buf<int>(inBP, inSz);
int* tmp = my_acc1::alloc_buf<int>(tmpBP, tmpSz);
my_acc1::compute(in, tmp, ...);
...;
return ...;
});
my_acc2::send_while(
[=]()->bool {
int* tmp;
bool cond = my_acc1::receive_one_in_order( // or receive_one_asap
[=, &tmp]() {
tmp = my_acc1::transfer_buf<int>(tmpBP);
});
if (!cond) return false;
int* out = my_acc2::alloc_buf<int>(outBP, outSz);
my_acc2::compute(tmp, out, ...);
...;
return true;
});
my_acc2::receive_all_in_order(
[=]() {
int* out = xfilter1::get_buf<int>(outBP);
...;
});
my_acc1::join();
my_acc2::join();