Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
99d5517
Add Engram contrib op schemas
Copilot Aug 25, 2026
b44523f
Add CPU Engram contrib kernels
Copilot Aug 25, 2026
819e695
Add CUDA Engram contrib kernels
Copilot Aug 25, 2026
608c54f
Add WebGPU Engram contrib kernels
Copilot Aug 25, 2026
3ad3490
Add Engram contrib op tests
Copilot Aug 25, 2026
3fd904b
Fix Engram CPU threadpool usage
Copilot Aug 25, 2026
1c82a47
Split Engram ops into standalone files
Copilot Aug 25, 2026
b8a41e7
Add CPU FP16 Engram kernels, shared EP helpers, and docs
Copilot Aug 25, 2026
fcb7ca0
Rename NgramHashMapping to NGramHashMapping
Copilot Aug 25, 2026
2ddc306
Address PR review: shared-gate/RMS passes and sign(0) fix for Engram ops
Copilot Aug 25, 2026
e22b5c8
Fix CI failures: CUDA GetComputeStream, WebGPU namespace, test skip trap
Copilot Aug 26, 2026
aaad3f7
Address review: preprojected EngramGate inputs, cross-invocation stat…
Copilot Aug 27, 2026
4c54142
Add WebGPU state kernels, equivalence tests, and regenerated contrib …
Copilot Aug 27, 2026
913a4f4
Write present_state on the empty-sequence path in all ShortConv EPs
Copilot Aug 27, 2026
0cd1275
Update OperatorKernels.md for the new Engram op signatures
Copilot Aug 27, 2026
fb32151
Address review: raw ShortConv state, engram_helper rename, exact mult…
Copilot Aug 28, 2026
8e89bcf
Remove unused chunk_config local in ShortConv chunked test (-Werror fix)
Copilot Aug 28, 2026
3749862
Vectorize WebGPU Engram shaders and cover int32 chunked n-gram path
Copilot Aug 31, 2026
ec7bcd6
Address review: validate vocab_sizes, temp allocator + loop hoisting,…
Copilot Sep 1, 2026
b907fdf
Remove ShortConv op; use CausalConvWithState instead
Copilot Sep 1, 2026
fe54d5c
Clarify multipliers/vocab_sizes descriptions as conventions, not cons…
Copilot Sep 1, 2026
c70e9d0
Add dilation attribute to CausalConvWithState schema and CPU kernel
Copilot Sep 1, 2026
772561e
Add dilation support to CausalConvWithState CUDA/WebGPU kernels, test…
Copilot Sep 1, 2026
a6d194e
Changes before error encountered
Copilot Sep 1, 2026
8d512ca
Add channels_last support to CausalConvWithState CPU/CUDA/WebGPU kernels
Copilot Sep 1, 2026
45de634
Add dilation support to VarlenCausalConvWithState CUDA kernels
Copilot Sep 1, 2026
82ff51e
Initial plan
Copilot Sep 1, 2026
3b15c9c
Add CUDA EP coverage, channels_last and varlen dilation tests, and up…
Copilot Sep 1, 2026
7fff3ae
Document why the varlen state shift is one sample regardless of dilation
Copilot Sep 1, 2026
698dbf7
Merge remote-tracking branch 'origin/main' into copilot/add-op-suppor…
Copilot Sep 1, 2026
69a8370
Add packed/varlen batching support for NGramHashMapping (VarlenNGramH…
Copilot Sep 1, 2026
608d15f
Define the missing MakeRamp test helper in engram_ops_test.cc
Copilot Sep 1, 2026
146fbc6
Merge remote-tracking branch 'origin/copilot/add-op-support-for-deeps…
Copilot Sep 1, 2026
4a25183
Remove duplicate ngram_hash_mapping.h include flagged by review
Copilot Sep 1, 2026
f5fb54c
Fix clang-format lint failures flagged by lintrunner on the PR
Copilot Sep 1, 2026
8edda99
Merge remote-tracking branch 'origin/main' into copilot/update-ngramh…
Copilot Sep 2, 2026
9c25337
Fix missing test body/closing brace introduced while re-merging engra…
Copilot Sep 2, 2026
9dfbea6
Fix corrupted merge scaffolding and address PR review feedback
Copilot Sep 3, 2026
321c191
Fix PR review 5107886057: global cu_seqlens validation, overflow guar…
Copilot Sep 4, 2026
f26f8b4
Add checked output and WebGPU dimension bounds
Copilot Sep 4, 2026
2a31b00
Format CUDA validation changes
Copilot Sep 4, 2026
c2985b2
Fix CUDA adapter context stream access
Copilot Sep 4, 2026
562f8af
Fix optional WebGPU output and shape validation
Copilot Sep 5, 2026
d803879
Merge remote-tracking branch 'origin/main' into copilot/update-ngramh…
Copilot Sep 8, 2026
4f62321
Destroy CUDA graph session before user stream
Copilot Sep 8, 2026
9450571
Merge remote-tracking branch 'origin/main' into copilot/update-ngramh…
Copilot Sep 8, 2026
54e91eb
Merge remote-tracking branch 'origin/main' into copilot/update-ngramh…
Copilot Sep 10, 2026
2e3c694
Parallelize varlen n-gram hashing
Copilot Sep 10, 2026
ed1c326
Merge remote-tracking branch 'origin/main' into copilot/update-ngramh…
Copilot Sep 11, 2026
aac26c7
Fix unused varlen hashing variable
Copilot Sep 11, 2026
3bd4671
Merge main and fix CUDA varlen aliasing
Copilot Sep 18, 2026
2ea6d6e
Format CUDA varlen alias fix
Copilot Sep 18, 2026
c2ebc8b
Merge remote-tracking branch 'origin/main' into copilot/update-ngramh…
Copilot Sep 28, 2026
903c89b
Fix varlen CUDA aliasing contract
Copilot Sep 28, 2026
2fb887d
Guard CUDA-only regression helper
Copilot Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions docs/ContribOperators.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Do not modify directly.*
* <a href="#com.microsoft.UnfoldTensor">com.microsoft.UnfoldTensor</a>
* <a href="#com.microsoft.Unique">com.microsoft.Unique</a>
* <a href="#com.microsoft.VarlenCausalConvWithState">com.microsoft.VarlenCausalConvWithState</a>
* <a href="#com.microsoft.VarlenNGramHashMapping">com.microsoft.VarlenNGramHashMapping</a>
* <a href="#com.microsoft.WhisperBeamSearch">com.microsoft.WhisperBeamSearch</a>
* <a href="#com.microsoft.WordConvEmbedding">com.microsoft.WordConvEmbedding</a>
* <sub>experimental</sub> <a href="#com.microsoft.IsAllFinite">com.microsoft.IsAllFinite</a>
Expand Down Expand Up @@ -7400,6 +7401,82 @@ This version of the operator has been available since version 1 of the 'com.micr
</dl>


### <a name="com.microsoft.VarlenNGramHashMapping"></a><a name="com.microsoft.varlenngramhashmapping">**com.microsoft.VarlenNGramHashMapping**</a>

Computes Engram n-gram hash ids from pre-compressed tokenizer ids over a packed, token-major batch
of variable-length sequences.

input_ids has shape (total_tokens) and hash_ids has shape (total_tokens, (max_ngram_size - 1) *
n_head_per_ngram). cumulative_sequence_length is a device-resident int32 tensor of shape
(batch_size + 1); request i occupies [cumulative_sequence_length[i], cumulative_sequence_length[i +
1]) of the packed buffer. Every request contributes at least one token.

For n in [2, max_ngram_size], the op creates causal shifts of each request's own tokens, padding
positions before that request's start with pad_id (or its own past_ids history, see below), and
computes mix = shifted_0 * multipliers[0] xor ... xor shifted_(n-1) * multipliers[n-1]. For every
head of that n-gram order it emits mix modulo the corresponding head vocabulary size. The n-gram
window never reads tokens belonging to a different packed request; it is clamped at each request's
own boundary exactly like VarlenCausalConvWithState clamps its causal convolution.

An n-gram window reaches max_ngram_size - 1 positions before the current token. To keep the op
causal across invocations (chunked prefill or autoregressive decode) for every concurrent request in
the packed batch, the optional past_ids input carries those preceding ids per request and
present_ids returns the ids to pass to the next call. Both have shape (batch_size, max_ngram_size -
1) and are right-aligned, so the last slot is the most recent id, and are indexed by request
(batch_size), not by position in the packed buffer. Positions before the start of a request's whole
sequence use pad_id. Running NGramHashMapping once per sequence and running this op once over those
sequences packed together (optionally split into packed chunks with present_ids threaded into
past_ids) produce identical hash ids.

#### Version

This version of the operator has been available since version 1 of the 'com.microsoft' operator set.

#### Attributes

<dl>
<dt><tt>max_ngram_size</tt> : int (required)</dt>
<dd>Maximum n-gram order. Must be at least 2.</dd>
<dt><tt>n_head_per_ngram</tt> : int (required)</dt>
<dd>Number of hash heads emitted for each n-gram order.</dd>
<dt><tt>pad_id</tt> : int (required)</dt>
<dd>Compressed tokenizer id used to pad causal shifts before the beginning of a request's sequence.</dd>
</dl>

#### Inputs (4 - 5)

<dl>
<dt><tt>input_ids</tt> : M</dt>
<dd>Token-major packed compressed tokenizer ids with shape (total_tokens).</dd>
<dt><tt>multipliers</tt> : M</dt>
<dd>Per-shift hash multipliers with shape (max_ngram_size). Conventionally odd, but any value is accepted.</dd>
<dt><tt>vocab_sizes</tt> : M</dt>
<dd>Per-output-head vocabulary sizes, conventionally prime, with shape ((max_ngram_size - 1) * n_head_per_ngram). Every entry must be strictly positive. The CPU implementation rejects a non-positive entry; GPU implementations guard the modulo to avoid a device-side division by zero and emit a hash id of 0 for that head.</dd>
<dt><tt>cumulative_sequence_length</tt> : S</dt>
<dd>Device tensor with shape (batch_size + 1) giving the half-open packed token range of each request.</dd>
<dt><tt>past_ids</tt> (optional) : M</dt>
<dd>Optional compressed tokenizer ids for the max_ngram_size - 1 positions that precede this call, with shape (batch_size, max_ngram_size - 1). Right-aligned, so the last slot is the most recent id, and indexed by request rather than by packed position. If omitted the history is pad_id.</dd>
</dl>

#### Outputs (1 - 2)

<dl>
<dt><tt>hash_ids</tt> : M</dt>
<dd>Token-major packed hash ids with shape (total_tokens, (max_ngram_size - 1) * n_head_per_ngram).</dd>
<dt><tt>present_ids</tt> (optional) : M</dt>
<dd>Trailing max_ngram_size - 1 ids of past_ids followed by each request's own tokens, with shape (batch_size, max_ngram_size - 1). Feed this back as past_ids on the next call.</dd>
</dl>

#### Type Constraints

<dl>
<dt><tt>M</tt> : tensor(int32), tensor(int64)</dt>
<dd>Constrain ids, multipliers, vocabulary sizes, and output ids to integer tensors.</dd>
<dt><tt>S</tt> : tensor(int32)</dt>
<dd>Constrain cumulative_sequence_length to a device int32 tensor.</dd>
</dl>


### <a name="com.microsoft.WhisperBeamSearch"></a><a name="com.microsoft.whisperbeamsearch">**com.microsoft.WhisperBeamSearch**</a>

Beam Search for whisper model, especially with cross_qk features etc.
Expand Down
2 changes: 2 additions & 0 deletions docs/OperatorKernels.md
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ The **OpSet Version** column uses the following notation:
|Trilu|*in* X:**T**<br> *in* k:**tensor(int64)**<br> *out* Y:**T**|1+|**T** = tensor(double), tensor(float), tensor(int64)|
|UnfoldTensor|*in* input:**T**<br> *out* output:**T**|1+|**T** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(string), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
|Unique|*in* x:**T**<br> *out* y:**T**<br> *out* idx:**tensor(int64)**<br> *out* counts:**tensor(int64)**|1+|**T** = tensor(float)|
|VarlenNGramHashMapping|*in* input_ids:**M**<br> *in* multipliers:**M**<br> *in* vocab_sizes:**M**<br> *in* cumulative_sequence_length:**S**<br> *in* past_ids:**M**<br> *out* hash_ids:**M**<br> *out* present_ids:**M**|1+|**M** = tensor(int32), tensor(int64)<br/> **S** = tensor(int32)|
|WhisperBeamSearch|*in* input_ids:**F**<br> *in* max_length:**I**<br> *in* min_length:**I**<br> *in* num_beams:**I**<br> *in* num_return_sequences:**I**<br> *in* length_penalty:**T**<br> *in* repetition_penalty:**T**<br> *in* vocab_mask:**M**<br> *in* prefix_vocab_mask:**M**<br> *in* attention_mask:**I**<br> *in* decoder_input_ids:**I**<br> *in* logits_processor:**I**<br> *in* cross_qk_layer_head:**I**<br> *in* extra_decoding_ids:**I**<br> *in* temperature:**T**<br> *out* sequences:**I**<br> *out* sequences_scores:**T**<br> *out* scores:**T**<br> *out* cross_qk:**V**<br> *out* non_speech_probs:**T**|1+|**T** = tensor(float)|
|WordConvEmbedding|*in* Sequence:**T**<br> *in* W:**T1**<br> *in* B:**T1**<br> *in* C:**T1**<br> *out* Y:**T1**|1+|**T** = tensor(int32)<br/> **T1** = tensor(float)|
| |
Expand Down Expand Up @@ -1147,6 +1148,7 @@ The **OpSet Version** column uses the following notation:
|Trilu|*in* X:**T**<br> *in* k:**tensor(int64)**<br> *out* Y:**T**|1+|**T** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
|UnfoldTensor|*in* input:**T**<br> *out* output:**T**|1+|**T** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(string), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
|VarlenCausalConvWithState|*in* input:**T**<br> *in* weight:**T**<br> *in* cumulative_sequence_length:**M**<br> *in* bias:**T**<br> *in* initial_state:**T**<br> *in* capture_count:**M**<br> *out* output:**T**<br> *out* final_state:**T**<br> *out* state_update:**T**|1+|**M** = tensor(int32)<br/> **T** = tensor(bfloat16), tensor(float), tensor(float16)|
|VarlenNGramHashMapping|*in* input_ids:**M**<br> *in* multipliers:**M**<br> *in* vocab_sizes:**M**<br> *in* cumulative_sequence_length:**S**<br> *in* past_ids:**M**<br> *out* hash_ids:**M**<br> *out* present_ids:**M**|1+|**M** = tensor(int32), tensor(int64)<br/> **S** = tensor(int32)|
|WhisperBeamSearch|*in* input_ids:**F**<br> *in* max_length:**I**<br> *in* min_length:**I**<br> *in* num_beams:**I**<br> *in* num_return_sequences:**I**<br> *in* length_penalty:**T**<br> *in* repetition_penalty:**T**<br> *in* vocab_mask:**M**<br> *in* prefix_vocab_mask:**M**<br> *in* attention_mask:**I**<br> *in* decoder_input_ids:**I**<br> *in* logits_processor:**I**<br> *in* cross_qk_layer_head:**I**<br> *in* extra_decoding_ids:**I**<br> *in* temperature:**T**<br> *out* sequences:**I**<br> *out* sequences_scores:**T**<br> *out* scores:**T**<br> *out* cross_qk:**V**<br> *out* non_speech_probs:**T**|1+|**T** = tensor(float), tensor(float16)|
| |
| |
Expand Down
16 changes: 16 additions & 0 deletions onnxruntime/contrib_ops/cpu/bert/engram_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <limits>
#include <type_traits>

namespace onnxruntime {
Expand Down Expand Up @@ -49,6 +51,20 @@ inline T WrappedMultiply(T a, T b) {
return static_cast<T>(static_cast<UnsignedT>(a) * static_cast<UnsignedT>(b));
}

// Multiplies two non-negative, attribute-derived dimensions (e.g. (max_ngram_size - 1) *
// n_head_per_ngram) with an overflow check. Both factors are user-controlled model attributes with
// only lower-bound checks, so the product must be validated before it is used to size any tensor or
// index: an unchecked signed multiplication would otherwise be free to silently wrap (undefined
// behavior in the general case) before ever reaching a tensor-shape validation. Returns false, and
// leaves `result` unset, when the product would overflow int64_t.
inline bool TryMultiplyDims(int64_t a, int64_t b, int64_t& result) {
if (a != 0 && b > std::numeric_limits<int64_t>::max() / a) {
return false;
}
result = a * b;
return true;
}

} // namespace engram_helper
} // namespace contrib
} // namespace onnxruntime
174 changes: 174 additions & 0 deletions onnxruntime/contrib_ops/cpu/bert/varlen_ngram_hash_mapping.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
// Licensed under the MIT License.

#include "contrib_ops/cpu/bert/varlen_ngram_hash_mapping.h"

#include <cstdint>
#include <limits>

#include "contrib_ops/cpu/bert/engram_helper.h"
#include "core/common/narrow.h"
#include "core/platform/threadpool.h"

using onnxruntime::concurrency::ThreadPool;

namespace onnxruntime {
namespace contrib {

#define REGISTER_VARLEN_NGRAM_HASH_TYPED(T) \
ONNX_OPERATOR_TYPED_KERNEL_EX( \
VarlenNGramHashMapping, \
kMSDomain, \
1, \
T, \
kCpuExecutionProvider, \
KernelDefBuilder() \
.TypeConstraint("M", DataTypeImpl::GetTensorType<T>()) \
.TypeConstraint("S", DataTypeImpl::GetTensorType<int32_t>()), \
VarlenNGramHashMapping<T>);

REGISTER_VARLEN_NGRAM_HASH_TYPED(int32_t)
REGISTER_VARLEN_NGRAM_HASH_TYPED(int64_t)

#undef REGISTER_VARLEN_NGRAM_HASH_TYPED

template <typename T>
VarlenNGramHashMapping<T>::VarlenNGramHashMapping(const OpKernelInfo& info) : OpKernel(info) {
ORT_ENFORCE(info.GetAttr<int64_t>("max_ngram_size", &max_ngram_size_).IsOK(),
"max_ngram_size attribute is required");
ORT_ENFORCE(info.GetAttr<int64_t>("n_head_per_ngram", &n_head_per_ngram_).IsOK(),
"n_head_per_ngram attribute is required");
int64_t pad_id = 0;
ORT_ENFORCE(info.GetAttr<int64_t>("pad_id", &pad_id).IsOK(), "pad_id attribute is required");
ORT_ENFORCE(max_ngram_size_ >= 2, "max_ngram_size must be at least 2");
ORT_ENFORCE(n_head_per_ngram_ >= 1, "n_head_per_ngram must be positive");
ORT_ENFORCE(pad_id >= static_cast<int64_t>(std::numeric_limits<T>::min()) &&
pad_id <= static_cast<int64_t>(std::numeric_limits<T>::max()),
"pad_id is out of range for the input id type");
pad_id_ = static_cast<T>(pad_id);
}

// Reads the id at right-aligned history slot `slot` of past_ids for request `b`. Slots outside the
// provided history (or a missing past_ids) are positions before the start of the whole sequence, so
// they use pad_id.
template <typename T>
T VarlenNGramHashMapping<T>::HistoryId(const T* past_data, int64_t b, int64_t slot, int64_t state_length) const {
if (past_data == nullptr || slot < 0 || slot >= state_length) {
return pad_id_;
}
return past_data[b * state_length + slot];
}

template <typename T>
Status VarlenNGramHashMapping<T>::Compute(OpKernelContext* context) const {
const Tensor* input_ids = context->Input<Tensor>(0);
const Tensor* multipliers = context->Input<Tensor>(1);
const Tensor* vocab_sizes = context->Input<Tensor>(2);
const Tensor* cu_seqlens = context->Input<Tensor>(3);
const Tensor* past_ids = context->Input<Tensor>(4);

ORT_RETURN_IF_NOT(input_ids->Shape().NumDimensions() == 1, "input_ids must have rank 1 (total_tokens)");
ORT_RETURN_IF_NOT(multipliers->Shape().NumDimensions() == 1 &&
multipliers->Shape()[0] == max_ngram_size_,
"multipliers must have shape (max_ngram_size)");
int64_t num_heads = 0;
ORT_RETURN_IF_NOT(engram_helper::TryMultiplyDims(max_ngram_size_ - 1, n_head_per_ngram_, num_heads),
"VarlenNGramHashMapping: (max_ngram_size - 1) * n_head_per_ngram overflows int64_t");
ORT_RETURN_IF_NOT(vocab_sizes->Shape().NumDimensions() == 1 && vocab_sizes->Shape()[0] == num_heads,
"vocab_sizes must have shape ((max_ngram_size - 1) * n_head_per_ngram)");
ORT_RETURN_IF_NOT(cu_seqlens->Shape().NumDimensions() == 1 && cu_seqlens->Shape()[0] >= 2,
"cumulative_sequence_length must have rank 1 with at least 2 elements");

const int64_t total_tokens = input_ids->Shape()[0];
const int64_t batch_size = cu_seqlens->Shape()[0] - 1;
ORT_RETURN_IF_NOT(total_tokens >= batch_size,
"total_tokens must be at least batch_size because every request must contain a token");
const int64_t state_length = max_ngram_size_ - 1;
if (past_ids != nullptr) {
ORT_RETURN_IF_NOT(past_ids->Shape() == TensorShape({batch_size, state_length}),
"past_ids must have shape (batch_size, max_ngram_size - 1)");
}

const int32_t* cu_data = cu_seqlens->Data<int32_t>();
ORT_RETURN_IF_NOT(cu_data[0] == 0, "cumulative_sequence_length[0] must be 0");
ORT_RETURN_IF_NOT(static_cast<int64_t>(cu_data[batch_size]) == total_tokens,
"cumulative_sequence_length[batch_size] must equal total_tokens");
Comment thread
kunal-vaishnavi marked this conversation as resolved.
for (int64_t b = 0; b < batch_size; ++b) {
ORT_RETURN_IF_NOT(cu_data[b] >= 0 && cu_data[b] < cu_data[b + 1],
"cumulative_sequence_length must be strictly increasing and non-negative "
"because every request must contain at least one token");
}

Tensor* output = context->Output(0, TensorShape({total_tokens, num_heads}));
Tensor* present_ids = context->Output(1, TensorShape({batch_size, state_length}));

const T* input_data = input_ids->Data<T>();
const T* multiplier_data = multipliers->Data<T>();
const T* vocab_data = vocab_sizes->Data<T>();
const T* past_data = past_ids == nullptr ? nullptr : past_ids->Data<T>();

// A non-positive head vocabulary size has no meaningful modulo. Every EP guards the division to
// avoid a device-side divide-by-zero, which turns the mistake into a constant hash id of 0 for that
// head rather than a crash. That is a silent wrong answer, so validate it here where vocab_sizes is
// already resident on the host and the check costs one pass over a tiny tensor.
for (int64_t h = 0; h < num_heads; ++h) {
ORT_RETURN_IF_NOT(vocab_data[h] > 0,
"vocab_sizes must be positive; entry ", h, " is ", static_cast<int64_t>(vocab_data[h]));
}

T* present_data = present_ids == nullptr ? nullptr : present_ids->MutableData<T>();
T* output_data = total_tokens == 0 ? nullptr : output->MutableData<T>();

ThreadPool::TryParallelFor(
context->GetOperatorThreadPool(), narrow<ptrdiff_t>(batch_size),
static_cast<double>((total_tokens > 0 ? total_tokens / std::max<int64_t>(batch_size, 1) : 1) *
max_ngram_size_ * n_head_per_ngram_),
Comment thread
kunal-vaishnavi marked this conversation as resolved.
Outdated
[&](ptrdiff_t begin, ptrdiff_t end) {
for (int64_t b = begin; b < end; ++b) {
const int64_t start = cu_data[b];
const int64_t seq_end = cu_data[b + 1];
const int64_t local_length = seq_end - start;

// present_ids is the right-aligned trailing window of (past_ids ++ this request's tokens),
// so it is well defined even when this call is shorter than the window. It never reads
// across a sequence boundary into an adjacent packed request.
if (present_data != nullptr) {
for (int64_t j = 0; j < state_length; ++j) {
const int64_t source_t = local_length - state_length + j;
present_data[b * state_length + j] =
source_t >= 0 ? input_data[start + source_t]
: HistoryId(past_data, b, state_length + source_t, state_length);
}
}

for (int64_t t = 0; t < local_length; ++t) {
const int64_t output_base = (start + t) * num_heads;
for (int64_t n = 2; n <= max_ngram_size_; ++n) {
T mix = 0;
for (int64_t k = 0; k < n; ++k) {
const int64_t source_t = t - k;
const T token = source_t >= 0 ? input_data[start + source_t]
: HistoryId(past_data, b, state_length + source_t, state_length);
const T product = engram_helper::WrappedMultiply(token, multiplier_data[k]);
mix = k == 0 ? product : static_cast<T>(mix ^ product);
}

const int64_t ngram_offset = (n - 2) * n_head_per_ngram_;
for (int64_t h = 0; h < n_head_per_ngram_; ++h) {
const int64_t out_h = ngram_offset + h;
// vocab_sizes was validated to be positive above, so the modulo is always well defined.
output_data[output_base + out_h] = engram_helper::PositiveMod(mix, vocab_data[out_h]);
}
}
}
}
});

return Status::OK();
}

template class VarlenNGramHashMapping<int32_t>;
template class VarlenNGramHashMapping<int64_t>;

} // namespace contrib
} // namespace onnxruntime
27 changes: 27 additions & 0 deletions onnxruntime/contrib_ops/cpu/bert/varlen_ngram_hash_mapping.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once

#include "core/common/common.h"
#include "core/framework/op_kernel.h"

namespace onnxruntime {
namespace contrib {

template <typename T>
class VarlenNGramHashMapping final : public OpKernel {
public:
explicit VarlenNGramHashMapping(const OpKernelInfo& info);
Status Compute(OpKernelContext* context) const override;

private:
T HistoryId(const T* past_data, int64_t b, int64_t slot, int64_t state_length) const;

int64_t max_ngram_size_;
int64_t n_head_per_ngram_;
T pad_id_;
};

} // namespace contrib
} // namespace onnxruntime
Loading
Loading