performance.now 将 onnxruntime 用于顺序和并行执行模式

performance.now using onnxruntime for sequential and parallel execution modes

我在 NodeJS 中使用 Onnxruntime 在 cpu 后端执行 onnx 转换模型。我 运行 使用 Promise.allSettled:

并行建模推理
var promises = sequences.map(seq => self.inference(self.session, self.tokenizer, seq));
results = (await Promise.allSettled(promises)).filter(p => p.status === "fulfilled").map(p => p.value);
        

运行调用静态方法 Util.performance.now

的 class 实例方法 inference
  ONNX.prototype.inference = async function (session, tokenizer, text) {
        const default_labels = this._options.model.default_labels;
        const labels = this._options.model.labels;
        const debug = this._options.debug;
        try {
            const encoded_ids = await tokenizer.tokenize(text);
            if (encoded_ids.length === 0) {
                return [0.0, default_labels];
            }
            const model_input = ONNX.create_model_input(encoded_ids);
            const start = Util.performance.now();
            const output = await session.run(model_input, ['output_0']);
            const duration = Util.performance.now(start).toFixed(1);

            const sequence_length = model_input['input_ids'].size;
            if (debug) console.log("latency = " + duration + "ms, sequence_length=" + sequence_length);
            const probs = output['output_0'].data.map(ONNX.sigmoid).map(t => Math.floor(t * 100));

            const result = [];
            for (var i = 0; i < labels.length; i++) {
                const t = [labels[i], probs[i]];
                result[i] = t;
            }
            result.sort(ONNX.sortResult);

            const result_list = [];
            for (i = 0; i < 6; i++) {
                result_list[i] = result[i];
            }
            return [parseFloat(duration), result_list];
        } catch (e) {
            return [0.0, default_labels];
        }
    }//inference

时机不对,总结一下。 performance 对象看起来像

Util = {
   performance: {
      now: function (start) {
          if (!start) {
              return process.hrtime();
          }
          var end = process.hrtime(start);
          return Math.round((end[0] * 1000) + (end[1] / 1000000));
      }
  }
}

并且按常规使用

// this runs parallel
const start = Util.performance.now();
// computation
const duration = (Util.performance.now() - start).toFixed(1);

现在,在 performance fun 中,startend 变量作用域是局部的,那么使用 Promise.allSettled 会发生什么?由于本地范围,我希望时间是正确的。

计时机制是正确的,但是当调用 session.run 时,它将启动一些异步 API (non-JavaScript),同时已经返回挂起的承诺对象。这将允许 inference 的其他执行也调用 session.run,导致异步 API 正在同时处理多个此类请求的状态,因此 相同 时间片被计算在 inference 的多个执行上下文中。这些请求甚至可能在非常接近的时刻结束。当发生这种情况时,inference 的另一个执行将继续执行结束计时的代码(设置它们的 duration 变量)。很明显,这些持续时间可以而且可能 重叠 彼此。

要将其可视化为 inference 的 3 次执行,您可以这样:

   start-----------------------------start+duration
      start-----------------------------start+duration
         start------------------------------start+duration

 time ----->

如果你不想要这种并行性,你不应该几乎同时调用所有 inference,而是等待每次下一次执行,直到上一次执行:

for (let seq of sequences) {
     let value = await self.inference(self.session, self.tokenizer, seq));
     // ...
}

这样 session.run 的异步部分的执行不会重叠,也不会因为并发而影响性能。我希望时间安排更像这样:

   start---------------start+duration
                       start-------------start+duration
                                         start------------start+duration

 time ----->

现在时间片不会被计算多次,即使整个过程的总持续时间可能会更长。

请注意,该行为与 Promise.allSettled 无关,因为您无论如何都会得到这些输出,即使您从程序中删除 Promise.allSettled 调用也是如此。