Files
fanlusky 80b1fc42a1 feat(engine): 词格整句改用 kenlm 三元模型打分
词格原先的整句打分是一份没校准过的启发式 unigram,质量很差。这里内嵌 libime
在用的 kenlm 查询子集,改由它的三元模型 sc.lm 来打路径分。

- engine/ngram 只编 kenlm 的 query / trie-build 路径。不定义 WITH_THREADS,
  parallel_read.cc 因此退化成串行实现,整个子集零 Boost 依赖。
  KENLM_MAX_ORDER=3 必须与建模阶数一致,改它等于读不懂已发布的模型文件。
- ngram::LanguageModel 是唯一 include kenlm 头文件的地方,对外只给出一个纯
  std 的头,_HAS_AUTO_PTR_ETC 和 kenlm 的 include 目录都保持 PRIVATE。
- shared_language_model 按资源路径共享一份 mmap。kenlm 的查询是只读的且把
  上下文放在调用方的 State 里,所以并发会话不需要额外加锁。
- 模型缺失时 valid() 为假,词格退回原来的启发式打分:整句候选只是变差,不会
  停止工作。测试覆盖了缺模型降级与按路径共享。
- 出货的 ARPA 不带 <s>/</s>,所以解码从 null_state() 起算而非 begin_state()。

kenlm 是上游副本,不重排版、不进 clang-format,其 LGPL-2.1+ 与 BSD 例外清单
已补进 THIRD_PARTY_NOTICES.txt。googlepinyinime 那条 Fallback 整句路径不变。
2026-09-19 10:08:39 +08:00

216 lines
6.4 KiB
C++

#ifndef UTIL_SIZED_ITERATOR_H
#define UTIL_SIZED_ITERATOR_H
#include "pool.hh"
#include "proxy_iterator.hh"
#include <algorithm>
#include <functional>
#include <string>
#include <stdint.h>
#include <cstring>
#include <stdlib.h>
namespace util {
class SizedInnerIterator {
public:
SizedInnerIterator() {}
SizedInnerIterator(void *ptr, std::size_t size) : ptr_(static_cast<uint8_t*>(ptr)), size_(size) {}
bool operator==(const SizedInnerIterator &other) const {
return ptr_ == other.ptr_;
}
bool operator<(const SizedInnerIterator &other) const {
return ptr_ < other.ptr_;
}
SizedInnerIterator &operator+=(std::ptrdiff_t amount) {
ptr_ += amount * size_;
return *this;
}
std::ptrdiff_t operator-(const SizedInnerIterator &other) const {
return (ptr_ - other.ptr_) / size_;
}
const void *Data() const { return ptr_; }
void *Data() { return ptr_; }
std::size_t EntrySize() const { return size_; }
friend void swap(SizedInnerIterator &first, SizedInnerIterator &second);
private:
uint8_t *ptr_;
std::size_t size_;
};
inline void swap(SizedInnerIterator &first, SizedInnerIterator &second) {
using std::swap;
swap(first.ptr_, second.ptr_);
swap(first.size_, second.size_);
}
class ValueBlock {
public:
explicit ValueBlock(const void *from, FreePool &pool)
: ptr_(std::memcpy(pool.Allocate(), from, pool.ElementSize())),
pool_(pool) {}
ValueBlock(const ValueBlock &from)
: ptr_(std::memcpy(from.pool_.Allocate(), from.ptr_, from.pool_.ElementSize())),
pool_(from.pool_) {}
ValueBlock &operator=(const ValueBlock &from) {
std::memcpy(ptr_, from.ptr_, pool_.ElementSize());
return *this;
}
~ValueBlock() { pool_.Free(ptr_); }
const void *Data() const { return ptr_; }
void *Data() { return ptr_; }
private:
void *ptr_;
FreePool &pool_;
};
class SizedProxy {
public:
SizedProxy() {}
SizedProxy(void *ptr, FreePool &pool) : inner_(ptr, pool.ElementSize()), pool_(&pool) {}
operator ValueBlock() const {
return ValueBlock(inner_.Data(), *pool_);
}
SizedProxy &operator=(const SizedProxy &from) {
memcpy(inner_.Data(), from.inner_.Data(), inner_.EntrySize());
return *this;
}
SizedProxy &operator=(const ValueBlock &from) {
memcpy(inner_.Data(), from.Data(), inner_.EntrySize());
return *this;
}
const void *Data() const { return inner_.Data(); }
void *Data() { return inner_.Data(); }
friend void swap(SizedProxy first, SizedProxy second);
private:
friend class util::ProxyIterator<SizedProxy>;
typedef ValueBlock value_type;
typedef SizedInnerIterator InnerIterator;
InnerIterator &Inner() { return inner_; }
const InnerIterator &Inner() const { return inner_; }
InnerIterator inner_;
FreePool *pool_;
};
inline void swap(SizedProxy first, SizedProxy second) {
std::swap_ranges(
static_cast<char*>(first.inner_.Data()),
static_cast<char*>(first.inner_.Data()) + first.inner_.EntrySize(),
static_cast<char*>(second.inner_.Data()));
}
typedef ProxyIterator<SizedProxy> SizedIterator;
// Useful wrapper for a comparison function i.e. sort.
template <class Delegate, class Proxy = SizedProxy> class SizedCompare : public std::binary_function<const Proxy &, const Proxy &, bool> {
public:
explicit SizedCompare(const Delegate &delegate = Delegate()) : delegate_(delegate) {}
bool operator()(const Proxy &first, const Proxy &second) const {
return delegate_(first.Data(), second.Data());
}
bool operator()(const Proxy &first, const ValueBlock &second) const {
return delegate_(first.Data(), second.Data());
}
bool operator()(const ValueBlock &first, const Proxy &second) const {
return delegate_(first.Data(), second.Data());
}
bool operator()(const ValueBlock &first, const ValueBlock &second) const {
return delegate_(first.Data(), second.Data());
}
const Delegate &GetDelegate() const { return delegate_; }
private:
const Delegate delegate_;
};
template <unsigned Size> class JustPOD {
unsigned char data[Size];
};
template <class Delegate, unsigned Size> class JustPODDelegate : std::binary_function<const JustPOD<Size> &, const JustPOD<Size> &, bool> {
public:
explicit JustPODDelegate(const Delegate &compare) : delegate_(compare) {}
bool operator()(const JustPOD<Size> &first, const JustPOD<Size> &second) const {
return delegate_(&first, &second);
}
private:
Delegate delegate_;
};
#define UTIL_SORT_SPECIALIZE(Size) \
case Size: \
std::sort(static_cast<JustPOD<Size>*>(start), static_cast<JustPOD<Size>*>(end), JustPODDelegate<Compare, Size>(compare)); \
break;
template <class Compare> void SizedSort(void *start, void *end, std::size_t element_size, const Compare &compare) {
switch (element_size) {
// Benchmarking sort found it's about 2x faster with an explicitly sized type. So here goes :-(.
UTIL_SORT_SPECIALIZE(4);
UTIL_SORT_SPECIALIZE(8);
UTIL_SORT_SPECIALIZE(12);
UTIL_SORT_SPECIALIZE(16);
UTIL_SORT_SPECIALIZE(17); // This is used by interpolation.
UTIL_SORT_SPECIALIZE(20);
UTIL_SORT_SPECIALIZE(24);
UTIL_SORT_SPECIALIZE(28);
UTIL_SORT_SPECIALIZE(32);
default:
// Recent g++ versions create a temporary value_type then compare with it.
// Problem is that value_type in this case needs to be a runtime-sized array.
// Previously I had std::string serve this role. However, there were a lot
// of string new and delete calls.
//
// The temporary value is on the stack, so there will typically only be one
// at a time. But we can't guarantee that. So here is a pool optimized for
// the case where one element is allocated at any given time. It can
// allocate more, should the underlying C++ sort code change.
{
FreePool pool(element_size);
// TODO is this necessary anymore?
#if defined(_WIN32) || defined(_WIN64)
std::stable_sort
#else
std::sort
#endif
(SizedIterator(SizedProxy(start, pool)), SizedIterator(SizedProxy(end, pool)), SizedCompare<Compare>(compare));
}
}
}
} // namespace util
// Dirty hack because g++ 4.6 at least wants to do a bunch of copy operations.
namespace std {
inline void iter_swap(util::SizedIterator first, util::SizedIterator second) {
util::swap(*first, *second);
}
} // namespace std
#endif // UTIL_SIZED_ITERATOR_H