Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ if(BUILD_TESTING)
# 跨进程学习与故障隔离。 / Cross-process learning and failure isolation.
add_executable(learning_integration_tests tests/learning_integration_tests.cpp)
target_link_libraries(learning_integration_tests PRIVATE same_core)
target_compile_definitions(learning_integration_tests PRIVATE SAME_TEST_ROOT="${CMAKE_CURRENT_SOURCE_DIR}")
if(WIN32)
target_link_libraries(learning_integration_tests PRIVATE shell32)
endif()
Expand Down
2 changes: 2 additions & 0 deletions docs/contextual-learning.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ predicted_ms = xᵀ β_backend
四项分别表示固定成本、字节成本、批量提交成本和与 payload 交互的竞争成本。CPU/iGPU 的 C 包括共享主机资源的在途任务;CUDA 使用同后端任务数。C 是轻量代理而非带宽测量。误差诊断始终比较**选择时冻结的预测**与结果,拟合则使用实际观测到的重叠上下文,避免把事后信息误称为决策时已知。设备的名称、厂商、驱动、实现版本、架构、计算单元、硬件线程、内存约束、统一内存属性和实际批量参与设备身份键;它们**不是全部作为回归变量输入**。因此这是本机按设备拟合,不是跨任意 GPU 的零样本性能预测。
The four terms represent fixed, byte, batch and payload–contention costs. Decision prediction uses the selection-time context; fitting uses observed overlap, while the error diagnostic stays tied to the actual decision-time prediction. Contention is still a proxy, not a bandwidth meter or a causal estimate. Device characteristics namespace learned state; this does not promise zero-shot transfer.

旧版本把选择时的在途数用于拟合,新版本则使用任务期间观测到的重叠;虽然四维公式未变,样本特征的**语义**已经变化。新模型身份为 `same-learning-key-v2`,不会把 v0.6.0 的 `v1` 统计与新标签混用。旧数据库与来源账本保留,不清空用户状态;第一次新版本运行需要重新积累或执行 `train`,后续相同工作区、设备与配置仍会正常加载 v2 先验。summary 的 `Model identity` 行可用于识别这一有意的冷启动。/ The feature formula is unchanged but its contention-label semantics changed. `same-learning-key-v2` isolates new observations from v0.6.0 v1 state without deleting old rows. The first new run must relearn or retrain; subsequent matching runs load v2 priors.

## 数学模型 / Mathematical model

每后端维护可加充分统计量(sufficient statistics):S=Σxxᵀ、t=Σxy、q=Σy² 和权重 W,以及特征范围。固定 4×4 数组,无逐样本历史分配。前四个本轮样本每次拟合,之后每 16 个样本重拟合,预测使用缓存系数。求解采用对角尺度归一化的岭回归(ridge regression):
Expand Down
6 changes: 6 additions & 0 deletions docs/igpu-worker-concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ uses the decision-time prediction, so it does not benefit from information that
became available only after dispatch. This remains a coarse contention proxy;
overlap duration and DRAM traffic are not observed.

The fitting-context change is not compatible with old v0.6.0 learned
statistics even though the four algebraic features have the same shape. The
application uses `same-learning-key-v2` for all three backends, preserving old
database rows while preventing mixed-semantic priors. This intentionally
introduces one cold-learning run after upgrade; later runs reuse v2 evidence.

## Local validation evidence

Windows x64 MSVC 19.51 CPU-only Debug CTest passed 27/27 after the
Expand Down
8 changes: 7 additions & 1 deletion src/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ void prepare_state(const fs::path& root) {
}
/// 运行边界遗忘,不随工作线程数变化。 / Run-boundary forgetting, independent of worker count.
constexpr double learning_decay = 0.9;
/// 新标签使用实际重叠上下文,必须与旧的选择时标签分开存放。
/// Observed-overlap labels must not mix with prior selection-time labels.
constexpr std::string_view learning_identity = "same-learning-key-v2";
/// 长度前缀消除字段拼接歧义,超长身份拒绝学习而不截断碰撞。
/// Length prefixes avoid ambiguous identities; reject oversize keys rather than truncate.
std::string learning_key(const Config& config, const DeviceProfile& cpu, BackendKind backend,
Expand All @@ -216,7 +219,7 @@ std::string learning_key(const Config& config, const DeviceProfile& cpu, Backend
number(value.effective_batch_bytes);
number(value.unified_memory);
};
add("same-learning-key-v1");
add(learning_identity);
number(detail::OnlineModel::feature_version);
number(detail::OnlineModel::feature_count);
add(backend_name(backend));
Expand Down Expand Up @@ -413,6 +416,8 @@ void render_learning(const Learning& learning, bool enabled, std::ostream& out,
report.row("Persistence", learning.store ? "enabled" : "disabled",
learning.store ? ReportTone::good : ReportTone::warning);
report.row("Model database", ".same/model.db", ReportTone::info);
report.row("Model identity", std::string(learning_identity) + " | observed overlap",
ReportTone::info);
report.row("Disabled reason",
learning.disabled_reason.empty() ? "none" : learning.disabled_reason,
learning.disabled_reason.empty() ? ReportTone::muted : ReportTone::warning);
Expand Down Expand Up @@ -442,6 +447,7 @@ void render_learning(const Learning& learning, bool enabled, std::ostream& out,
<< " model_persistence_disabled_reason="
<< (learning.disabled_reason.empty() ? "none" : learning.disabled_reason)
<< " model_db=.same/model.db"
<< " model_identity=" << learning_identity
<< " model_load_attempts=" << learning.loads.load()
<< " model_prior_hits=" << learning.hits.load()
<< " model_invalid=" << learning.invalid.load() << " model_saved_keys=" << learning.saved
Expand Down
48 changes: 47 additions & 1 deletion tests/learning_integration_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,24 @@ std::string stored_key(const fs::path& path) {
sqlite3_close(db);
return key;
}
/// 测试升级场景时仅移除当前身份,保留旧版来源贡献。
/// Remove only the current identity while retaining the legacy contribution for upgrade tests.
void remove_key(const fs::path& path, std::string_view key) {
sqlite3* db{};
check(sqlite3_open(utf8(path).c_str(), &db) == SQLITE_OK, "open model for identity test");
for (const auto* table : {"models", "model_parts"}) {
const auto sql = std::string("DELETE FROM ") + table + " WHERE key=?1";
sqlite3_stmt* statement{};
check(sqlite3_prepare_v2(db, sql.c_str(), -1, &statement, nullptr) == SQLITE_OK,
"prepare identity removal");
check(sqlite3_bind_blob(statement, 1, key.data(), static_cast<int>(key.size()),
SQLITE_TRANSIENT) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE,
"remove current identity");
sqlite3_finalize(statement);
}
sqlite3_close(db);
}
/// 汇总中的明确计数,不解析展示顺序。 / Extract an explicit summary count independent of ordering.
std::uint64_t metric(const std::string& text, const std::string& name) {
std::smatch match;
Expand All @@ -197,7 +215,7 @@ int main(int argc, char** argv) {
try {
check(argc == 2, "expected CLI executable");
const auto exe = executable_path(argv[1]);
base = fs::canonical(fs::temp_directory_path()) /
base = fs::canonical(fs::path(SAME_TEST_ROOT)) / ".temp" /
("same-learning-" +
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
const auto root = base / "root";
Expand Down Expand Up @@ -263,6 +281,34 @@ int main(int argc, char** argv) {
check(std::abs(second[0].weight - (0.9 * first[0].weight + 16)) < 1e-8,
"decayed prior not merged exactly once");
}
// v0.6.0 的旧特征标签不能被新的实际重叠语义复用,但数据库须保持可写。
// Old selection-time labels must not load as observed-overlap priors; DB remains writable.
const auto current_prefix = std::string("same-learning-key-v2");
const auto prefix_at = key.find(current_prefix);
check(prefix_at != std::string::npos, "new model identity namespace missing");
auto legacy_key = key;
legacy_key.replace(prefix_at, current_prefix.size(), "same-learning-key-v1");
same::detail::OnlineModel::State legacy{};
{
same::ModelStore store(database);
legacy = *store.load(key);
check(store.save(legacy_key, legacy), "cannot seed legacy model contribution");
}
remove_key(database, key);
run();
check(metric(read(err), "model_prior_hits") == 0 &&
metric(read(err), "model_persistence_enabled") == 1 && read(out) == expected,
"legacy model identity leaked into new routing or disabled persistence");
{
same::ModelStore store(database);
check(store.load(legacy_key) &&
store.load(legacy_key)->at(0).samples == legacy[0].samples &&
store.load(key) && store.load(key)->at(0).samples == 16,
"new training erased legacy state or failed to create isolated state");
}
run();
check(metric(read(err), "model_prior_hits") == 2,
"new identity did not resume normal cross-run reuse");
const auto before = read(database);
run(false);
check(read(database) == before, "no-pgo changed model database");
Expand Down
Loading