Merge pull request #594 from luojiyin1987/perf/neural-skip-padding

perf(engine): skip unused neural scoring positions
This commit is contained in:
Lu Fan(SonnyCalcr) authored and GitHub committed 2026-10-03 17:46:28 +08:00
commit 1ae218ecd4
2 files changed
+51 -14

No files matched your search

+34 -14
View File
@@ -849,7 +849,22 @@ std::vector<float> SentenceModel::target_log_probs(const std::vector<float> &hid
{
const std::size_t n_embd = config_.n_embd;
const std::size_t vocab = config_.vocab;
std::vector<float> logits(rows * vocab, 0.0f);
std::vector<std::size_t> active_rows;
active_rows.reserve(rows);
for (std::size_t row = 0; row < rows; ++row)
{
if (targets[row] != kNoTarget)
{
active_rows.push_back(row);
}
}
std::vector<float> out(rows, 0.0f);
if (active_rows.empty())
{
return out;
}
std::vector<float> logits(active_rows.size() * vocab, 0.0f);
// Output-major over the vocabulary, and widening each row's int8 once, for the same reasons as
// linear(): the tied embedding table is the single largest matrix in the model.
std::vector<float> scratch(token_.quantized ? n_embd : 0, 0.0f);
@@ -871,20 +886,17 @@ std::vector<float> SentenceModel::target_log_probs(const std::vector<float> &hid
{
weights = &token_.floats[id * n_embd];
}
for (std::size_t row = 0; row < rows; ++row)
for (std::size_t slot = 0; slot < active_rows.size(); ++slot)
{
logits[row * vocab + id] = dot(&hidden[row * n_embd], weights, n_embd) * scale;
const std::size_t row = active_rows[slot];
logits[slot * vocab + id] = dot(&hidden[row * n_embd], weights, n_embd) * scale;
}
}
std::vector<float> out(rows, 0.0f);
for (std::size_t row = 0; row < rows; ++row)
for (std::size_t slot = 0; slot < active_rows.size(); ++slot)
{
if (targets[row] == kNoTarget)
{
continue;
}
const float *source = &logits[row * vocab];
const std::size_t row = active_rows[slot];
const float *source = &logits[slot * vocab];
float highest = -std::numeric_limits<float>::infinity();
for (std::size_t id = 0; id < vocab; ++id)
{
@@ -953,12 +965,17 @@ std::vector<double> SentenceModel::score_sentences(const std::string &context,
longest = std::max(longest, ids.size());
tails.push_back(std::move(ids));
}
const std::size_t width = longest + 1; // the shared leading token, then the longest candidate
if (longest == 0)
{
return std::vector<double>(texts.size(), 0.0);
}
// The last candidate token is a target only. Its hidden state predicts no scored character.
const std::size_t width = longest;
const std::uint32_t last = full.back();
std::vector<std::uint32_t> head(full.begin(), full.end() - 1);
// Head plus the widest row must fit the position table; the oldest context goes first.
const std::size_t room = limit > width ? limit - width : 0;
// Keep the old context limit so removing the unused final token does not change scores.
const std::size_t room = limit > longest + 1 ? limit - (longest + 1) : 0;
if (head.size() > room)
{
head.erase(head.begin(), head.begin() + static_cast<std::ptrdiff_t>(head.size() - room));
@@ -973,9 +990,12 @@ std::vector<double> SentenceModel::score_sentences(const std::string &context,
flat[row * width] = last;
for (std::size_t i = 0; i < tails[row].size(); ++i)
{
flat[row * width + 1 + i] = tails[row][i];
// Position i predicts token i+1, so the leading token's slot carries the first character.
targets[row * width + i] = tails[row][i];
if (i + 1 < tails[row].size())
{
flat[row * width + 1 + i] = tails[row][i];
}
}
}
+17
View File
@@ -153,6 +153,23 @@ int run_test()
}
}
// Empty candidates have no targets. They must not read a padding logit.
{
const std::vector<std::string> texts = {"", "上海", "", "伤害"};
const std::vector<double> scores = model->score_sentences("我今天想去", texts);
expect(scores.size() == texts.size(), "empty candidates should keep their batch positions");
if (scores.size() == texts.size())
{
for (std::size_t i = 0; i < texts.size(); ++i)
{
expect_near(scores[i], reference_score(*model, "我今天想去", texts[i]), 1e-3,
"empty candidates should not affect other scores");
}
}
expect(model->score_sentences("", {""}) == std::vector<double>{0.0},
"a batch without targets should score zero");
}
// ---- 批量与单条一致:padding 行不能污染同批的其它行 ----
{
const std::string context = "我今天想去";