Commit ·
844305a
1
Parent(s): 35e8531
Upload ChatIPC.cpp
Browse files- ChatIPC.cpp +121 -101
ChatIPC.cpp
CHANGED
|
@@ -468,20 +468,32 @@ struct PtrEq { bool operator()(StrPtr a, StrPtr b) const noexcept { return a =
|
|
| 468 |
|
| 469 |
using NextSet = std::vector<StrPtr>;
|
| 470 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
struct KnowledgeBase {
|
| 472 |
StringInterner interner;
|
| 473 |
-
|
| 474 |
-
std::unordered_map<std::
|
| 475 |
mutable std::mutex m;
|
| 476 |
|
| 477 |
std::unordered_map<StrPtr, std::vector<StrPtr>, PtrHash, PtrEq> def_index;
|
| 478 |
mutable std::mutex def_m;
|
| 479 |
int def_depth = 0;
|
| 480 |
|
| 481 |
-
void
|
|
|
|
|
|
|
|
|
|
| 482 |
std::lock_guard<std::mutex> lk(m);
|
| 483 |
-
|
| 484 |
-
auto &vec = next[k];
|
| 485 |
for (auto p : vec) if (p == v) return;
|
| 486 |
vec.push_back(v);
|
| 487 |
}
|
|
@@ -495,8 +507,7 @@ struct KnowledgeBase {
|
|
| 495 |
}
|
| 496 |
|
| 497 |
void ensure_def_for_interned(StrPtr wp){
|
| 498 |
-
if (wp == nullptr) return;
|
| 499 |
-
if (def_depth <= 0) return;
|
| 500 |
|
| 501 |
{
|
| 502 |
std::lock_guard<std::mutex> lk(def_m);
|
|
@@ -546,30 +557,26 @@ struct KnowledgeBase {
|
|
| 546 |
}
|
| 547 |
}
|
| 548 |
|
| 549 |
-
|
| 550 |
-
StrPtr kp = interner.intern(k);
|
| 551 |
-
StrPtr vp = interner.intern(v);
|
| 552 |
-
ensure_def_for_interned(kp);
|
| 553 |
-
ensure_def_for_interned(vp);
|
| 554 |
-
add_pair_interned(kp, vp);
|
| 555 |
-
}
|
| 556 |
-
|
| 557 |
-
std::optional<NextSet> lookup_by_string(const std::string &k) const {
|
| 558 |
std::lock_guard<std::mutex> lk(m);
|
| 559 |
-
auto
|
| 560 |
-
if (kit == next_key_index.end()) return std::nullopt;
|
| 561 |
-
auto it = next.find(kit->second);
|
| 562 |
if (it == next.end()) return std::nullopt;
|
| 563 |
return it->second;
|
| 564 |
}
|
|
|
|
| 565 |
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
}
|
| 572 |
-
}
|
| 573 |
|
| 574 |
static std::vector<StrPtr>
|
| 575 |
intern_tokens(KnowledgeBase &kb, const std::vector<std::string> &tokens)
|
|
@@ -872,14 +879,18 @@ static std::string best_candidate_by_similarity(
|
|
| 872 |
static std::vector<std::string> construct_response(KnowledgeBase &kb,
|
| 873 |
const std::vector<std::string> &prompt_toks,
|
| 874 |
size_t response_maxlen,
|
| 875 |
-
double repeat_penalty
|
|
|
|
| 876 |
{
|
| 877 |
std::vector<std::string> resp;
|
| 878 |
if (prompt_toks.empty() || response_maxlen == 0) return resp;
|
| 879 |
|
| 880 |
auto prompt_ptrs = intern_tokens(kb, prompt_toks);
|
| 881 |
std::vector<StrPtr> resp_ptrs;
|
| 882 |
-
std::unordered_map<std::string,int> recent_counts;
|
|
|
|
|
|
|
|
|
|
| 883 |
|
| 884 |
auto would_create_2_cycle = [&](const std::string &cand) -> bool {
|
| 885 |
if (resp.size() < 3) return false;
|
|
@@ -887,40 +898,37 @@ static std::vector<std::string> construct_response(KnowledgeBase &kb,
|
|
| 887 |
normalize_dictionary_key(resp.back()) == normalize_dictionary_key(resp[resp.size() - 3]);
|
| 888 |
};
|
| 889 |
|
| 890 |
-
std::string last_printed;
|
| 891 |
-
|
| 892 |
for (size_t step = 0; step < response_maxlen; ++step){
|
| 893 |
NextSet candidates;
|
| 894 |
bool found = false;
|
| 895 |
std::string context_tok;
|
| 896 |
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 901 |
candidates = *opt;
|
| 902 |
found = true;
|
| 903 |
-
context_tok =
|
| 904 |
break;
|
| 905 |
}
|
| 906 |
}
|
| 907 |
-
} else {
|
| 908 |
-
auto opt = kb.lookup_by_string(last_printed);
|
| 909 |
-
if (opt){
|
| 910 |
-
candidates = *opt;
|
| 911 |
-
found = true;
|
| 912 |
-
context_tok = last_printed;
|
| 913 |
-
} else {
|
| 914 |
-
for (ssize_t p = static_cast<ssize_t>(prompt_toks.size()) - 1; p >= 0; --p){
|
| 915 |
-
auto opt2 = kb.lookup_by_string(prompt_toks[(size_t)p]);
|
| 916 |
-
if (opt2){
|
| 917 |
-
candidates = *opt2;
|
| 918 |
-
found = true;
|
| 919 |
-
context_tok = prompt_toks[(size_t)p];
|
| 920 |
-
break;
|
| 921 |
-
}
|
| 922 |
-
}
|
| 923 |
-
}
|
| 924 |
}
|
| 925 |
|
| 926 |
if (!found || candidates.empty()) break;
|
|
@@ -931,9 +939,10 @@ static std::vector<std::string> construct_response(KnowledgeBase &kb,
|
|
| 931 |
if (recent_counts[only_key.empty() ? only : only_key] > 0) break;
|
| 932 |
|
| 933 |
resp.push_back(only);
|
| 934 |
-
|
|
|
|
|
|
|
| 935 |
recent_counts[only_key.empty() ? only : only_key] += 1;
|
| 936 |
-
last_printed = only;
|
| 937 |
std::cout << only << ' ' << std::flush;
|
| 938 |
continue;
|
| 939 |
}
|
|
@@ -947,37 +956,37 @@ static std::vector<std::string> construct_response(KnowledgeBase &kb,
|
|
| 947 |
if (would_create_2_cycle(chosen)) break;
|
| 948 |
|
| 949 |
resp.push_back(chosen);
|
| 950 |
-
|
|
|
|
|
|
|
| 951 |
|
| 952 |
std::string chosen_key = normalize_dictionary_key(chosen);
|
| 953 |
recent_counts[chosen_key.empty() ? chosen : chosen_key] += 1;
|
| 954 |
|
| 955 |
-
last_printed = chosen;
|
| 956 |
std::cout << chosen << ' ' << std::flush;
|
| 957 |
}
|
| 958 |
|
| 959 |
return resp;
|
| 960 |
}
|
| 961 |
|
| 962 |
-
static void learn_from_file(KnowledgeBase &kb, const std::string &fname){
|
| 963 |
std::ifstream ifs(fname);
|
| 964 |
if (!ifs) return;
|
|
|
|
| 965 |
std::string tok;
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
while (ifs >> tok){
|
| 969 |
-
if (have_prev) kb.add_pair(prev, tok);
|
| 970 |
-
prev = tok; have_prev = true;
|
| 971 |
-
}
|
| 972 |
}
|
| 973 |
|
| 974 |
-
static void learn_files_parallel(KnowledgeBase &kb, const std::vector<std::string> &files){
|
| 975 |
#pragma omp parallel for schedule(dynamic)
|
| 976 |
-
for (ptrdiff_t i=0;i<static_cast<ptrdiff_t>(files.size());++i)
|
|
|
|
|
|
|
| 977 |
}
|
| 978 |
|
| 979 |
static constexpr std::uint64_t KB_MAGIC = 0x434850434B535641ULL;
|
| 980 |
-
static constexpr std::uint64_t KB_VERSION =
|
| 981 |
|
| 982 |
static void write_u64(std::ostream &os, std::uint64_t v){
|
| 983 |
os.write(reinterpret_cast<const char*>(&v), sizeof(v));
|
|
@@ -1001,7 +1010,7 @@ static void write_string(std::ostream &os, const std::string &s){
|
|
| 1001 |
|
| 1002 |
static std::string read_string(std::istream &is){
|
| 1003 |
std::uint64_t n = read_u64(is);
|
| 1004 |
-
if (n > (1ULL << 30)) throw std::runtime_error("
|
| 1005 |
|
| 1006 |
std::string s;
|
| 1007 |
s.resize(static_cast<size_t>(n));
|
|
@@ -1041,7 +1050,10 @@ static void save_kb_binary(const KnowledgeBase &kb, const std::string &fname){
|
|
| 1041 |
|
| 1042 |
write_u64(ofs, static_cast<std::uint64_t>(kb.next.size()));
|
| 1043 |
for (const auto &pr : kb.next){
|
| 1044 |
-
write_u64(ofs,
|
|
|
|
|
|
|
|
|
|
| 1045 |
write_u64(ofs, static_cast<std::uint64_t>(pr.second.size()));
|
| 1046 |
for (StrPtr nxt : pr.second){
|
| 1047 |
write_u64(ofs, id.at(*nxt));
|
|
@@ -1081,7 +1093,7 @@ static void load_kb_binary(KnowledgeBase &kb, const std::string &fname, int cli_
|
|
| 1081 |
const std::uint64_t file_def_depth = read_u64(ifs);
|
| 1082 |
|
| 1083 |
const std::uint64_t N = read_u64(ifs);
|
| 1084 |
-
if (N > (1ULL << 26)) throw std::runtime_error("
|
| 1085 |
|
| 1086 |
std::vector<std::string> strings;
|
| 1087 |
strings.reserve(static_cast<size_t>(N));
|
|
@@ -1101,43 +1113,47 @@ static void load_kb_binary(KnowledgeBase &kb, const std::string &fname, int cli_
|
|
| 1101 |
|
| 1102 |
// Rebuild next
|
| 1103 |
const std::uint64_t E = read_u64(ifs);
|
| 1104 |
-
if (E > (1ULL << 26)) throw std::runtime_error("
|
| 1105 |
|
| 1106 |
{
|
| 1107 |
std::lock_guard<std::mutex> lk(kb.m);
|
| 1108 |
kb.next.clear();
|
| 1109 |
-
kb.next_key_index.clear();
|
| 1110 |
kb.next.reserve(static_cast<size_t>(E));
|
| 1111 |
-
kb.next_key_index.reserve(static_cast<size_t>(E));
|
| 1112 |
}
|
| 1113 |
|
| 1114 |
for (std::uint64_t i = 0; i < E; ++i){
|
| 1115 |
-
const std::uint64_t
|
| 1116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1117 |
|
| 1118 |
-
|
| 1119 |
-
if (M > (1ULL << 26)) throw std::runtime_error("
|
| 1120 |
|
| 1121 |
-
StrPtr key_ptr = ptrs[(size_t)key_idx];
|
| 1122 |
NextSet vec;
|
| 1123 |
vec.reserve(static_cast<size_t>(M));
|
| 1124 |
|
| 1125 |
for (std::uint64_t j = 0; j < M; ++j){
|
| 1126 |
const std::uint64_t v_idx = read_u64(ifs);
|
| 1127 |
-
if (v_idx >= ptrs.size()) throw std::runtime_error("
|
| 1128 |
vec.push_back(ptrs[(size_t)v_idx]);
|
| 1129 |
}
|
| 1130 |
|
| 1131 |
{
|
| 1132 |
std::lock_guard<std::mutex> lk(kb.m);
|
| 1133 |
-
kb.next.emplace(
|
| 1134 |
-
kb.next_key_index.emplace(*key_ptr, key_ptr);
|
| 1135 |
}
|
| 1136 |
}
|
| 1137 |
|
| 1138 |
// Rebuild def_index from file
|
| 1139 |
const std::uint64_t K = read_u64(ifs);
|
| 1140 |
-
if (K > (1ULL << 26)) throw std::runtime_error("
|
| 1141 |
|
| 1142 |
{
|
| 1143 |
std::lock_guard<std::mutex> lk(kb.def_m);
|
|
@@ -1150,15 +1166,15 @@ static void load_kb_binary(KnowledgeBase &kb, const std::string &fname, int cli_
|
|
| 1150 |
const std::uint64_t key_idx = read_u64(ifs);
|
| 1151 |
const std::uint64_t M = read_u64(ifs);
|
| 1152 |
|
| 1153 |
-
if (key_idx >= ptrs.size()) throw std::runtime_error("
|
| 1154 |
-
if (M > (1ULL << 26)) throw std::runtime_error("
|
| 1155 |
|
| 1156 |
std::vector<StrPtr> toks;
|
| 1157 |
toks.reserve(static_cast<size_t>(M));
|
| 1158 |
|
| 1159 |
for (std::uint64_t j = 0; j < M; ++j){
|
| 1160 |
const std::uint64_t v_idx = read_u64(ifs);
|
| 1161 |
-
if (v_idx >= ptrs.size()) throw std::runtime_error("
|
| 1162 |
toks.push_back(ptrs[(size_t)v_idx]);
|
| 1163 |
}
|
| 1164 |
|
|
@@ -1184,9 +1200,16 @@ static void load_kb_binary(KnowledgeBase &kb, const std::string &fname, int cli_
|
|
| 1184 |
{
|
| 1185 |
std::lock_guard<std::mutex> lk(kb.m);
|
| 1186 |
for (const auto &pr : kb.next){
|
| 1187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1188 |
for (StrPtr v : pr.second){
|
| 1189 |
-
if (seen.insert(v).second)
|
|
|
|
|
|
|
| 1190 |
}
|
| 1191 |
}
|
| 1192 |
}
|
|
@@ -1201,20 +1224,22 @@ static void load_kb_binary(KnowledgeBase &kb, const std::string &fname, int cli_
|
|
| 1201 |
static void print_usage(const char *p){
|
| 1202 |
std::cout << "CLI options: [--response-max-length N] [--save FILE] [--load-kb FILE] [--dictionary-depth D] [--learn f1 f2 ...] [--repeat-penalty P] [--help]\n";
|
| 1203 |
std::cout << " --response-max-length N Maximum number of tokens in a response.\n";
|
| 1204 |
-
std::cout << " --save FILE
|
| 1205 |
std::cout << " --load-kb FILE Load a previously saved knowledge-base from a binary file.\n";
|
| 1206 |
std::cout << " --dictionary-depth D Depth of dictionary-definition expansion used during learning.\n";
|
|
|
|
| 1207 |
std::cout << " --learn f1 f2 ... Learn from one or more text files to update the knowledge-base.\n";
|
| 1208 |
std::cout << " --repeat-penalty P Penalize repeated tokens when constructing response (higher values reduce repetition).\n";
|
| 1209 |
-
std::cout << " --help Show Command-Line Interface (CLI) options for using " << p << "\n";
|
| 1210 |
}
|
| 1211 |
|
| 1212 |
int main(int argc, char **argv){
|
| 1213 |
-
size_t response_maxlen =
|
| 1214 |
std::string savefile;
|
| 1215 |
std::string load_txt;
|
| 1216 |
std::string load_kb;
|
| 1217 |
-
int dict_depth =
|
|
|
|
| 1218 |
double repeat_penalty = 0.7; // default λ
|
| 1219 |
std::vector<std::string> learn_files;
|
| 1220 |
|
|
@@ -1222,18 +1247,13 @@ int main(int argc, char **argv){
|
|
| 1222 |
std::string a = argv[i];
|
| 1223 |
if (a=="--help"){ print_usage(argv[0]); return 0; }
|
| 1224 |
if (a=="--response-max-length" && i+1<argc){ response_maxlen = std::stoul(argv[++i]); continue; }
|
| 1225 |
-
if (a=="--save" && i+1<argc){ savefile = argv[++i]; continue; }
|
| 1226 |
if (a=="--load-kb" && i+1<argc){ load_kb = argv[++i]; continue; }
|
| 1227 |
if (a=="--dictionary-depth" && i+1<argc){ dict_depth = std::stoi(argv[++i]); continue; }
|
|
|
|
| 1228 |
if (a=="--repeat-penalty" && i+1<argc){ repeat_penalty = std::stod(argv[++i]); continue; }
|
| 1229 |
if (a=="--learn"){
|
| 1230 |
-
++i;
|
| 1231 |
-
for (; i<argc; ++i){
|
| 1232 |
-
if (!argv[i]) break;
|
| 1233 |
-
std::string s = argv[i];
|
| 1234 |
-
if (!s.empty() && s[0]=='-'){ --i; break; }
|
| 1235 |
-
learn_files.push_back(s);
|
| 1236 |
-
}
|
| 1237 |
continue;
|
| 1238 |
}
|
| 1239 |
learn_files.push_back(a);
|
|
@@ -1253,7 +1273,7 @@ int main(int argc, char **argv){
|
|
| 1253 |
|
| 1254 |
if (!learn_files.empty()){
|
| 1255 |
std::cerr << "Learning from file/s (" << learn_files.size() << ") using threads=" << omp_get_max_threads() << "\n";
|
| 1256 |
-
learn_files_parallel(kb, learn_files);
|
| 1257 |
}
|
| 1258 |
|
| 1259 |
std::string line;
|
|
@@ -1261,10 +1281,10 @@ int main(int argc, char **argv){
|
|
| 1261 |
while (std::cout << "> " , std::getline(std::cin, line)){
|
| 1262 |
if (line.empty()){ std::cout << "\n"; continue; }
|
| 1263 |
auto prompt_toks = tokenize_whitespace(line);
|
| 1264 |
-
|
| 1265 |
-
auto resp = construct_response(kb, prompt_toks, response_maxlen, repeat_penalty);
|
| 1266 |
std::cout << "\n";
|
| 1267 |
-
if (!resp.empty()){
|
| 1268 |
if (!savefile.empty()){
|
| 1269 |
try { std::cerr << "Saving knowledge base: " << savefile << "\n";
|
| 1270 |
save_kb_binary(kb, savefile); std::cerr << "Saved knowledge base: " << savefile << "\n"; }
|
|
|
|
| 468 |
|
| 469 |
using NextSet = std::vector<StrPtr>;
|
| 470 |
|
| 471 |
+
struct NgramHash {
|
| 472 |
+
std::size_t operator()(const std::vector<StrPtr>& v) const noexcept {
|
| 473 |
+
std::size_t seed = v.size();
|
| 474 |
+
for(auto& p : v) {
|
| 475 |
+
seed ^= std::hash<StrPtr>()(p) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
| 476 |
+
}
|
| 477 |
+
return seed;
|
| 478 |
+
}
|
| 479 |
+
};
|
| 480 |
+
|
| 481 |
struct KnowledgeBase {
|
| 482 |
StringInterner interner;
|
| 483 |
+
// Modified to use a vector of StrPtr (N-gram) as the key
|
| 484 |
+
std::unordered_map<std::vector<StrPtr>, NextSet, NgramHash> next;
|
| 485 |
mutable std::mutex m;
|
| 486 |
|
| 487 |
std::unordered_map<StrPtr, std::vector<StrPtr>, PtrHash, PtrEq> def_index;
|
| 488 |
mutable std::mutex def_m;
|
| 489 |
int def_depth = 0;
|
| 490 |
|
| 491 |
+
void add_ngram(const std::vector<StrPtr>& ctx, StrPtr v){
|
| 492 |
+
for (auto p : ctx) ensure_def_for_interned(p);
|
| 493 |
+
ensure_def_for_interned(v);
|
| 494 |
+
|
| 495 |
std::lock_guard<std::mutex> lk(m);
|
| 496 |
+
auto &vec = next[ctx];
|
|
|
|
| 497 |
for (auto p : vec) if (p == v) return;
|
| 498 |
vec.push_back(v);
|
| 499 |
}
|
|
|
|
| 507 |
}
|
| 508 |
|
| 509 |
void ensure_def_for_interned(StrPtr wp){
|
| 510 |
+
if (wp == nullptr || def_depth <= 0) return;
|
|
|
|
| 511 |
|
| 512 |
{
|
| 513 |
std::lock_guard<std::mutex> lk(def_m);
|
|
|
|
| 557 |
}
|
| 558 |
}
|
| 559 |
|
| 560 |
+
std::optional<NextSet> lookup_ngram(const std::vector<StrPtr>& ctx) const {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
std::lock_guard<std::mutex> lk(m);
|
| 562 |
+
auto it = next.find(ctx);
|
|
|
|
|
|
|
| 563 |
if (it == next.end()) return std::nullopt;
|
| 564 |
return it->second;
|
| 565 |
}
|
| 566 |
+
};
|
| 567 |
|
| 568 |
+
static void learn_tokens_ngram(KnowledgeBase &kb, const std::vector<std::string>& tokens, int n_gram_size) {
|
| 569 |
+
std::vector<StrPtr> window;
|
| 570 |
+
for (const auto& tok : tokens) {
|
| 571 |
+
StrPtr tp = kb.interner.intern(tok);
|
| 572 |
+
for (size_t j = 0; j < window.size(); ++j) {
|
| 573 |
+
std::vector<StrPtr> ctx(window.begin() + j, window.end());
|
| 574 |
+
kb.add_ngram(ctx, tp);
|
| 575 |
+
}
|
| 576 |
+
window.push_back(tp);
|
| 577 |
+
if (window.size() > static_cast<size_t>(n_gram_size)) window.erase(window.begin());
|
| 578 |
}
|
| 579 |
+
}
|
| 580 |
|
| 581 |
static std::vector<StrPtr>
|
| 582 |
intern_tokens(KnowledgeBase &kb, const std::vector<std::string> &tokens)
|
|
|
|
| 879 |
static std::vector<std::string> construct_response(KnowledgeBase &kb,
|
| 880 |
const std::vector<std::string> &prompt_toks,
|
| 881 |
size_t response_maxlen,
|
| 882 |
+
double repeat_penalty,
|
| 883 |
+
int n_gram_size)
|
| 884 |
{
|
| 885 |
std::vector<std::string> resp;
|
| 886 |
if (prompt_toks.empty() || response_maxlen == 0) return resp;
|
| 887 |
|
| 888 |
auto prompt_ptrs = intern_tokens(kb, prompt_toks);
|
| 889 |
std::vector<StrPtr> resp_ptrs;
|
| 890 |
+
std::unordered_map<std::string, int> recent_counts;
|
| 891 |
+
|
| 892 |
+
// The context combines prompt and generated text
|
| 893 |
+
std::vector<StrPtr> context = prompt_ptrs;
|
| 894 |
|
| 895 |
auto would_create_2_cycle = [&](const std::string &cand) -> bool {
|
| 896 |
if (resp.size() < 3) return false;
|
|
|
|
| 898 |
normalize_dictionary_key(resp.back()) == normalize_dictionary_key(resp[resp.size() - 3]);
|
| 899 |
};
|
| 900 |
|
|
|
|
|
|
|
| 901 |
for (size_t step = 0; step < response_maxlen; ++step){
|
| 902 |
NextSet candidates;
|
| 903 |
bool found = false;
|
| 904 |
std::string context_tok;
|
| 905 |
|
| 906 |
+
// Dynamic Backoff N-gram lookup
|
| 907 |
+
int current_n = std::min(static_cast<int>(context.size()), n_gram_size);
|
| 908 |
+
while (current_n > 0) {
|
| 909 |
+
std::vector<StrPtr> search_ctx(context.end() - current_n, context.end());
|
| 910 |
+
auto opt = kb.lookup_ngram(search_ctx);
|
| 911 |
+
if (opt && !opt->empty()) {
|
| 912 |
+
candidates = *opt;
|
| 913 |
+
found = true;
|
| 914 |
+
context_tok = *search_ctx.back();
|
| 915 |
+
break;
|
| 916 |
+
}
|
| 917 |
+
current_n--;
|
| 918 |
+
}
|
| 919 |
+
|
| 920 |
+
// Fallback to checking earlier prompt tokens if sequence stalled
|
| 921 |
+
if (!found) {
|
| 922 |
+
for (ssize_t p = static_cast<ssize_t>(prompt_ptrs.size()) - 1; p >= 0; --p){
|
| 923 |
+
std::vector<StrPtr> search_ctx = { prompt_ptrs[(size_t)p] };
|
| 924 |
+
auto opt = kb.lookup_ngram(search_ctx);
|
| 925 |
+
if (opt && !opt->empty()){
|
| 926 |
candidates = *opt;
|
| 927 |
found = true;
|
| 928 |
+
context_tok = *prompt_ptrs[(size_t)p];
|
| 929 |
break;
|
| 930 |
}
|
| 931 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 932 |
}
|
| 933 |
|
| 934 |
if (!found || candidates.empty()) break;
|
|
|
|
| 939 |
if (recent_counts[only_key.empty() ? only : only_key] > 0) break;
|
| 940 |
|
| 941 |
resp.push_back(only);
|
| 942 |
+
StrPtr ptr = kb.interner.intern(only);
|
| 943 |
+
resp_ptrs.push_back(ptr);
|
| 944 |
+
context.push_back(ptr);
|
| 945 |
recent_counts[only_key.empty() ? only : only_key] += 1;
|
|
|
|
| 946 |
std::cout << only << ' ' << std::flush;
|
| 947 |
continue;
|
| 948 |
}
|
|
|
|
| 956 |
if (would_create_2_cycle(chosen)) break;
|
| 957 |
|
| 958 |
resp.push_back(chosen);
|
| 959 |
+
StrPtr chosen_ptr = kb.interner.intern(chosen);
|
| 960 |
+
resp_ptrs.push_back(chosen_ptr);
|
| 961 |
+
context.push_back(chosen_ptr);
|
| 962 |
|
| 963 |
std::string chosen_key = normalize_dictionary_key(chosen);
|
| 964 |
recent_counts[chosen_key.empty() ? chosen : chosen_key] += 1;
|
| 965 |
|
|
|
|
| 966 |
std::cout << chosen << ' ' << std::flush;
|
| 967 |
}
|
| 968 |
|
| 969 |
return resp;
|
| 970 |
}
|
| 971 |
|
| 972 |
+
static void learn_from_file(KnowledgeBase &kb, const std::string &fname, int n_gram_size){
|
| 973 |
std::ifstream ifs(fname);
|
| 974 |
if (!ifs) return;
|
| 975 |
+
std::vector<std::string> tokens;
|
| 976 |
std::string tok;
|
| 977 |
+
while (ifs >> tok) tokens.push_back(tok);
|
| 978 |
+
learn_tokens_ngram(kb, tokens, n_gram_size);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 979 |
}
|
| 980 |
|
| 981 |
+
static void learn_files_parallel(KnowledgeBase &kb, const std::vector<std::string> &files, int n_gram_size){
|
| 982 |
#pragma omp parallel for schedule(dynamic)
|
| 983 |
+
for (ptrdiff_t i=0;i<static_cast<ptrdiff_t>(files.size());++i){
|
| 984 |
+
learn_from_file(kb, files[(size_t)i], n_gram_size);
|
| 985 |
+
}
|
| 986 |
}
|
| 987 |
|
| 988 |
static constexpr std::uint64_t KB_MAGIC = 0x434850434B535641ULL;
|
| 989 |
+
static constexpr std::uint64_t KB_VERSION = 2ULL;
|
| 990 |
|
| 991 |
static void write_u64(std::ostream &os, std::uint64_t v){
|
| 992 |
os.write(reinterpret_cast<const char*>(&v), sizeof(v));
|
|
|
|
| 1010 |
|
| 1011 |
static std::string read_string(std::istream &is){
|
| 1012 |
std::uint64_t n = read_u64(is);
|
| 1013 |
+
if (n > (1ULL << 30)) throw std::runtime_error("save file is corrupted: string too large");
|
| 1014 |
|
| 1015 |
std::string s;
|
| 1016 |
s.resize(static_cast<size_t>(n));
|
|
|
|
| 1050 |
|
| 1051 |
write_u64(ofs, static_cast<std::uint64_t>(kb.next.size()));
|
| 1052 |
for (const auto &pr : kb.next){
|
| 1053 |
+
write_u64(ofs, static_cast<std::uint64_t>(pr.first.size()));
|
| 1054 |
+
for (StrPtr ctx_tok : pr.first){
|
| 1055 |
+
write_u64(ofs, id.at(*ctx_tok));
|
| 1056 |
+
}
|
| 1057 |
write_u64(ofs, static_cast<std::uint64_t>(pr.second.size()));
|
| 1058 |
for (StrPtr nxt : pr.second){
|
| 1059 |
write_u64(ofs, id.at(*nxt));
|
|
|
|
| 1093 |
const std::uint64_t file_def_depth = read_u64(ifs);
|
| 1094 |
|
| 1095 |
const std::uint64_t N = read_u64(ifs);
|
| 1096 |
+
if (N > (1ULL << 26)) throw std::runtime_error("save file is corrupted: pool too large");
|
| 1097 |
|
| 1098 |
std::vector<std::string> strings;
|
| 1099 |
strings.reserve(static_cast<size_t>(N));
|
|
|
|
| 1113 |
|
| 1114 |
// Rebuild next
|
| 1115 |
const std::uint64_t E = read_u64(ifs);
|
| 1116 |
+
if (E > (1ULL << 26)) throw std::runtime_error("save file is corrupted: graph too large");
|
| 1117 |
|
| 1118 |
{
|
| 1119 |
std::lock_guard<std::mutex> lk(kb.m);
|
| 1120 |
kb.next.clear();
|
|
|
|
| 1121 |
kb.next.reserve(static_cast<size_t>(E));
|
|
|
|
| 1122 |
}
|
| 1123 |
|
| 1124 |
for (std::uint64_t i = 0; i < E; ++i){
|
| 1125 |
+
const std::uint64_t ctx_size = read_u64(ifs);
|
| 1126 |
+
if (ctx_size > (1ULL << 16)) throw std::runtime_error("save file is corrupted: n-gram context too large");
|
| 1127 |
+
|
| 1128 |
+
std::vector<StrPtr> ctx;
|
| 1129 |
+
ctx.reserve(static_cast<size_t>(ctx_size));
|
| 1130 |
+
for(std::uint64_t c = 0; c < ctx_size; ++c) {
|
| 1131 |
+
const std::uint64_t c_idx = read_u64(ifs);
|
| 1132 |
+
if (c_idx >= ptrs.size()) throw std::runtime_error("save file is corrupted: bad context key");
|
| 1133 |
+
ctx.push_back(ptrs[(size_t)c_idx]);
|
| 1134 |
+
}
|
| 1135 |
|
| 1136 |
+
const std::uint64_t M = read_u64(ifs);
|
| 1137 |
+
if (M > (1ULL << 26)) throw std::runtime_error("save file is corrupted: graph degree too large");
|
| 1138 |
|
|
|
|
| 1139 |
NextSet vec;
|
| 1140 |
vec.reserve(static_cast<size_t>(M));
|
| 1141 |
|
| 1142 |
for (std::uint64_t j = 0; j < M; ++j){
|
| 1143 |
const std::uint64_t v_idx = read_u64(ifs);
|
| 1144 |
+
if (v_idx >= ptrs.size()) throw std::runtime_error("save file is corrupted: bad graph value");
|
| 1145 |
vec.push_back(ptrs[(size_t)v_idx]);
|
| 1146 |
}
|
| 1147 |
|
| 1148 |
{
|
| 1149 |
std::lock_guard<std::mutex> lk(kb.m);
|
| 1150 |
+
kb.next.emplace(std::move(ctx), std::move(vec));
|
|
|
|
| 1151 |
}
|
| 1152 |
}
|
| 1153 |
|
| 1154 |
// Rebuild def_index from file
|
| 1155 |
const std::uint64_t K = read_u64(ifs);
|
| 1156 |
+
if (K > (1ULL << 26)) throw std::runtime_error("save file is corrupted: def_index too large");
|
| 1157 |
|
| 1158 |
{
|
| 1159 |
std::lock_guard<std::mutex> lk(kb.def_m);
|
|
|
|
| 1166 |
const std::uint64_t key_idx = read_u64(ifs);
|
| 1167 |
const std::uint64_t M = read_u64(ifs);
|
| 1168 |
|
| 1169 |
+
if (key_idx >= ptrs.size()) throw std::runtime_error("save file is corrupted: bad def key");
|
| 1170 |
+
if (M > (1ULL << 26)) throw std::runtime_error("save file is corrupted: def list too large");
|
| 1171 |
|
| 1172 |
std::vector<StrPtr> toks;
|
| 1173 |
toks.reserve(static_cast<size_t>(M));
|
| 1174 |
|
| 1175 |
for (std::uint64_t j = 0; j < M; ++j){
|
| 1176 |
const std::uint64_t v_idx = read_u64(ifs);
|
| 1177 |
+
if (v_idx >= ptrs.size()) throw std::runtime_error("save file is corrupted: bad def value");
|
| 1178 |
toks.push_back(ptrs[(size_t)v_idx]);
|
| 1179 |
}
|
| 1180 |
|
|
|
|
| 1200 |
{
|
| 1201 |
std::lock_guard<std::mutex> lk(kb.m);
|
| 1202 |
for (const auto &pr : kb.next){
|
| 1203 |
+
for (StrPtr ctx_element : pr.first) {
|
| 1204 |
+
if (seen.insert(ctx_element).second) {
|
| 1205 |
+
targets.push_back(ctx_element);
|
| 1206 |
+
}
|
| 1207 |
+
}
|
| 1208 |
+
|
| 1209 |
for (StrPtr v : pr.second){
|
| 1210 |
+
if (seen.insert(v).second) {
|
| 1211 |
+
targets.push_back(v);
|
| 1212 |
+
}
|
| 1213 |
}
|
| 1214 |
}
|
| 1215 |
}
|
|
|
|
| 1224 |
static void print_usage(const char *p){
|
| 1225 |
std::cout << "CLI options: [--response-max-length N] [--save FILE] [--load-kb FILE] [--dictionary-depth D] [--learn f1 f2 ...] [--repeat-penalty P] [--help]\n";
|
| 1226 |
std::cout << " --response-max-length N Maximum number of tokens in a response.\n";
|
| 1227 |
+
std::cout << " --save-kb FILE Save the knowledge-base to a binary file.\n";
|
| 1228 |
std::cout << " --load-kb FILE Load a previously saved knowledge-base from a binary file.\n";
|
| 1229 |
std::cout << " --dictionary-depth D Depth of dictionary-definition expansion used during learning.\n";
|
| 1230 |
+
std::cout << " --n-gram N Size of the n-gram where N is the size.\n";
|
| 1231 |
std::cout << " --learn f1 f2 ... Learn from one or more text files to update the knowledge-base.\n";
|
| 1232 |
std::cout << " --repeat-penalty P Penalize repeated tokens when constructing response (higher values reduce repetition).\n";
|
| 1233 |
+
std::cout << " --help Show all the Command-Line Interface (CLI) options for using " << p << "\n";
|
| 1234 |
}
|
| 1235 |
|
| 1236 |
int main(int argc, char **argv){
|
| 1237 |
+
size_t response_maxlen = 500;
|
| 1238 |
std::string savefile;
|
| 1239 |
std::string load_txt;
|
| 1240 |
std::string load_kb;
|
| 1241 |
+
int dict_depth = 3;
|
| 1242 |
+
int n_gram_size = 3;
|
| 1243 |
double repeat_penalty = 0.7; // default λ
|
| 1244 |
std::vector<std::string> learn_files;
|
| 1245 |
|
|
|
|
| 1247 |
std::string a = argv[i];
|
| 1248 |
if (a=="--help"){ print_usage(argv[0]); return 0; }
|
| 1249 |
if (a=="--response-max-length" && i+1<argc){ response_maxlen = std::stoul(argv[++i]); continue; }
|
| 1250 |
+
if (a=="--save-kb" && i+1<argc){ savefile = argv[++i]; continue; }
|
| 1251 |
if (a=="--load-kb" && i+1<argc){ load_kb = argv[++i]; continue; }
|
| 1252 |
if (a=="--dictionary-depth" && i+1<argc){ dict_depth = std::stoi(argv[++i]); continue; }
|
| 1253 |
+
if (a=="--n-gram" && i+1<argc){ n_gram_size = std::max(1, std::stoi(argv[++i])); continue; }
|
| 1254 |
if (a=="--repeat-penalty" && i+1<argc){ repeat_penalty = std::stod(argv[++i]); continue; }
|
| 1255 |
if (a=="--learn"){
|
| 1256 |
+
while(i+1<argc && argv[i+1][0] != '-') learn_files.push_back(argv[++i]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1257 |
continue;
|
| 1258 |
}
|
| 1259 |
learn_files.push_back(a);
|
|
|
|
| 1273 |
|
| 1274 |
if (!learn_files.empty()){
|
| 1275 |
std::cerr << "Learning from file/s (" << learn_files.size() << ") using threads=" << omp_get_max_threads() << "\n";
|
| 1276 |
+
learn_files_parallel(kb, learn_files, n_gram_size);
|
| 1277 |
}
|
| 1278 |
|
| 1279 |
std::string line;
|
|
|
|
| 1281 |
while (std::cout << "> " , std::getline(std::cin, line)){
|
| 1282 |
if (line.empty()){ std::cout << "\n"; continue; }
|
| 1283 |
auto prompt_toks = tokenize_whitespace(line);
|
| 1284 |
+
learn_tokens_ngram(kb, prompt_toks, n_gram_size);
|
| 1285 |
+
auto resp = construct_response(kb, prompt_toks, response_maxlen, repeat_penalty, n_gram_size);
|
| 1286 |
std::cout << "\n";
|
| 1287 |
+
if (!resp.empty()){learn_tokens_ngram(kb, resp, n_gram_size);}
|
| 1288 |
if (!savefile.empty()){
|
| 1289 |
try { std::cerr << "Saving knowledge base: " << savefile << "\n";
|
| 1290 |
save_kb_binary(kb, savefile); std::cerr << "Saved knowledge base: " << savefile << "\n"; }
|