用GPU加速拼音输入法?

看到IBM申请了用GPU加速数据库的专利,想起来自己之前还有过用GPU来加速拼音输入法的想法,当时写在了twitter上,幸亏当初有在google reader里订阅了twitter中有关sunpinyin的关键字搜索,否则还真翻不出来了呢…

Jun/16, 2011:

这几天我一直在想,能否用GPU来加速输入法,我可以把SunPinyin Syllable Graph的所有组合,发到GPU上去开足够多的线程做并行搜索,或者在做viterbi时,栅格间的状态节点转移,也可以放到GPU上来并行运算,可大幅度提高beem宽度… 个人倾向于第一种方式…

http://twitter.com/#!/yongsun/statuses/81130853278822400

当时还和熟悉GPU/OpenCL的@reminisce1989同学探讨来着,reminisce1989同学认为用GPU加速Graph算法是很困难的,不过后来reminisce1989同学好像删推了,没有原推可以refer了。

yongsun: @reminisce1989 @jjgod 既然是基础服务,肯定不能随便down掉,应该是可以想办法避免的… 我的想法主要是,现在sunpinyin的搜索规模还很小,不知道能否借助GPU来大幅提高搜索的规模,回头有问题还要向您请教了 :)

reminisce1989: @yongsun @jjgod 最后提醒一下,GPU是不支持指针的(或者说只能模拟指针)。而我不知道你的sunpinyin用的搜索是什么图算法。如果用到任何常规图论算法,不用说难用OpenCL之类的语言实现,实现了效率也提不上去,因为是非顺序化读取,而且还要考虑拷贝数据的时间。

后来,我仔细阅读了OpenGL的一些文章和书籍,觉得应该是有希望的,不过一直没有时间去做实验,先记录在这里吧…

open-gram词表协议改为Apache V2.0

经过社区的讨论,我们一致同意,将open-grame词表的协议改为Apache V2.0

  • 新的词库将以android-google-pinyin的词库为基础
  • 废弃之前基于cc-cedict的老词库,但是将open-gram自己发现/汇总的一些新词以Apache V2.0协议重新贡献给open-gram词表
  • libpinyin/novel-pinyin/smart-pinyin 项目lead已同意,将各自项目的词库,以Apache V2.0的协议贡献给open-gram项目
  • FIT团队同意将FIT输入法的网络热词,定期反馈给open-gram社区

非常感谢大家的支持和帮助!

SunPinyin的词库一直以来为大家所诟病,希望这次能为大家带来质量更好的词库,和更好的输入体验…

SunPinyin Developers on ChinaDaily/Video

ChinaDaily Video频道的于辰康记者,前几日采访了SunPinyin社区的开发者,非常感谢ChinaDaily以及于先生,给我们这次难得的机会。我们在面对镜头时都变得超紧张,经常NG,耽搁了于先生很多的时间,而于先生对我们则非常耐心。

因为节目时长的原因,我们在介绍社区成员时,没能介绍全部的贡献者,主要介绍了比较活跃的几位成员,而且在介绍时还有一些小瑕疵,希望大家谅解。我们本来邀请了Phill Zhang同学,但是不凑巧他实在抽不出时间,很是遗憾。远在外地的tchaikov, jjgod和mike同学,都只能以照片形式入镜了。mike同学超骨感的,我也是借这个机会,第一次看到mike同学的真容 :) 感谢所有的contributors和用户朋友们,感谢大家对sunpinyin的支持!

【预告】SunPinyin做客Beta沙龙

http://club.blogbeta.com/2010/05/13-sunpinyin

时间:2010年6月4日星期五晚上,19: 00开始
地点:奇遇花园咖啡馆(问路电话010-88320741)
地址:西直门北展北街9号华远企业号(华远地产)D座一层(地图

演讲题目:What’s new of SunPinyin 2.0

内容预览:

演讲人简介:
孙勇,SunPinyin项目的Lead Engineer之一,SunPinyin Mac Porting的主要维护者。
个人blog,http://yongsun.me,twitter: @yongsun

参与说明:无需报名,参加免费,点单AA

setup sunpinyin-developers@googlegroups.com

我们刚刚为SunPinyin项目建立了一个google-group。虽然oso-inputmethod项目也有一个mail-list,不过是和其他opensolaris i18n/l10n项目share的。而且似乎国内的开发者或用户,还是更习惯和倾向使用google-group作为交流的途径。因此我们就创建了这个group。

SunPinyin的开发者会使用这个mail group来讨论开发相关的问题,同时用户也可以用这个mail group来报告发现的问题。(觉得再建一个sunpinyin-users的group还不是这么紧迫和必要。)大家也可以直接发邮件到sunpinyin-developers@googlegroups.com

期待您的加入!

A simple event flow of SunPinyin2

sunpinyin_event_flow

顺便再补充一句,为了将来能让ime-core编译为一个shared object,SunPinyin2开始使用自己的key mapping。这个文件列出了一些特殊键(function key),它们的keycode和X11 keysym是一致的,因此和scim和ibus的keysym也是一致的。其他的键,例如a-z/A-Z/0-9等和ASCII兼容的,可以直接传入(其实这和X11的keysym还是一致的)。再以外的keycode就不处理直接返回了。因此各位在给SunPinyin2做porting时,还需要编写一个简单的键盘事件的影射。

What’s New in SunPinyin2 — Configuration and Session Factory

SunPinyin2在设计和实现的过程中,希望能同时支持简体、繁体的词典及语言模型,支持不同的拼音方案,支持不同的输入风格(包括微软拼音和类似sogou或google拼音)。不同的输入风格,对应了不同的view的实现。(虽然目前SunPinyin2还没有重新实现微软拼音的输入风格——CIMIModernView。)

这些选项组合起来数目繁多,不过它们都是正交的,正好可以让我们使用policy based的实现方式。

我们定义了一个CSunpinyinProfile的模板类,它继承了ISunpinyinProfile这个公共接口类(否则我无法声明一个保存其指针的container),和它的三个模板参数,LanguagePolicy,PinyinSchemePolicy,InputStylePolicy。它的createProfile方法,调用policy类中的相关方法,来创建具体的一个view实例。为了避免创建policy的实例,并且更重要的是要共享policy的公共数据,policy类都是单件(singleton)。

LanguagePolicy负责初始化词典、用户词典和语言模型,以及根据这些资源创建CIMIContext类的实例。CSimplifiedChinesePolicy是一个具体的实现。另外还有一些方法,可以让你设置CIMIContext类实例的缺省属性。例如,enableFullPunctenableFullSymbolsetPunctMapping方法设置s_getFullPunctOp(被所有由该policy类创建的CIMIContext实例所共享)的标点符号影射。我们之前总结为shared & global的用户配置项。可以设想,用户在设置对话框自行定义标点符号的映射关系后,程序员只需要调用CSimplifiedChinesePolicy::setPunctMapping。这个配置项的更改会应用到所有现有的输入上下文(或会话),对输入上下文来说是透明的。

PinyinSchemePolicy负责创建拼音切分器的实例。CQuanpinSchemePolicyCShuangpinSchemePolicy是两个具体的实现。以CQuanpinSchemePolicy类为例,createPySegmentor方法创建了CQuanpinSegmentor的一个实例并返回。和CSimplifiedChinesePolicy类似,CQuanpinSchemePolicy也有一些shared & global的配置项,例如是否支持易混淆音和自动纠错。

InputStylePolicy负责创建CIMIView的实例。CClassicStylePolicy是一个具体的实现。它只是简单的创建一个CIMIClassicView的实例并返回。CSunPinyinProfile<...>::createProfile方法虽然也是返回一个view的实例,不过这个view是已经设置停当、可以工作的view。

CSunpinyinSessionFactory是一个singleton的工厂类,其createSession方法根据factory中policy的设定,调用某个CSunpinyinProfile实例的createProfile方法,创建一个具体的view。要添加新的语言支持或拼音方案,不仅要完成自己的那部分coding,还要记得编写相应的policy classes,并将支持的所有组合注册到CSunpinyinSessionFactory中(没办法,在这个地方就只能穷举了)。

因为我们最后expose给外部的是一个view的实例,所以如果用户的配置涉及到对policy的切换(我们之前总结为non-shared but global的配置项),以前创建的输入上下文(即view),就面临一个抉择。要么保持不变,继续使用其目前的输入风格;要么要重新创建,discard掉以前上下文的状态信息(例如preedit/candidates)。通常,我们对某个平台的移植,都是创建一个包含view的会话类,这个会话类适配该平台的接口,并将输入事件filter给view。那么我们的这些会话类是如何知道发生了non-shared but global选项的改变呢?

CSunpinyinSessionFactory类提供了一个简单的方法,updateToken/getToken。当平台相关的会话类调用CSunpinyinSessionFactory类创建一个view的实例,同时应该调用getToken得到一个token(口令)。当会话类得到焦点的时候,它可以调用getToken再次得到一个token,比较与自己持有的这个token是否一致。如果不一致,就表明发生了non-shared but global选项的更改。与此相对应地,如果用户对non-shared but global的选项发生了修改,程序员应该要调用updateToken去更新factory当前的token。

这个方式有些丑陋,稍微改观一点的方法可能是,我们再加入一个抽象层次,例如CSunpinyinSession,然后它用signal/slot的方式connect到factory的reset_session信号。另一个问题,配置项还是比较分散,如果要编写一个集中的CSunpinyinOption类的话,就会有很多duplicated的代码。我对上面这些问题,还没有想得很清楚,也非常欢迎大家多提宝贵意见!:)

行文至此,我的“What's new in SunPinyin2”系列也就暂时告一段落了。希望对William、以及SunPinyin2的移植工作有所帮助。我还会继续竭尽全力,投入到SunPinyin2的开发和移植工作中 :)

What’s New in SunPinyin2 — User Dictionary

原先的SunPinyin没有用户词典,只是通过User History Cache纪录了用户近期输入的bi-gram信息。如果一个bi-gram,其概率比系统词典的某个uni-gram要低,这个bi-gram的组合就不会出现在用户的候选列表中。例如,虽然用户频繁的输入钥匙,但是它依然很难出现在候选列表中,因为“要是”这个unigram的概率要更高。如果“钥匙”出现在候选中,一定是第一候选,因为它是作为一个最佳候选句子来呈现给用户的。如果用户选择了“要是”,那么“钥匙”又不知道要过多久才能出现了。这个缺陷也是广受大家诟病的地方。其实,SunPinyin原先的架构是支持将用户自定义词放到lattice上进行搜索的

我们在SunPinyin2中,通过sqlite3来实现用户词典,目前我们的用户词典支持记录<=6个的字符。

CUserDict::_createTable/_createIndexes:

这两个私有方法尝试创建一个table和index。这个表的结构是,首先词的ID(从1开始的)和长度,接下来是6个声母,然后是6个韵母,再其次是6个声调。然后我们为长度+6个声母建立了一个索引。sqlite的query对搜索条件和index有一些限制,要求条件的次序和index的次序相同,并且一旦某个条件是非相等性的(例如>),则该条件及其之后的条件都无法用index来加速。另外,每个from子句只能使用一个index。

因为我们经常会有不完整拼音的查询,或者换句话说用户经常会输入不完整拼音,所以table和index就被设计成了这个样子。

CUserDict::addWord (syllables, word):

使用了sqlite中的prepared statement来进行插入。最后,在插入成功的情况下,调用sqlite3_last_insert_rowid得到上一次插入记录的row id,加上一个offset并返回。如果失败,就返回0。

CUserDict::removeWord (wid):

这个相对简单,按照wid将记录从数据库中删除。

CUserDict::getWords (syllables, result):

这个方法将syllables在数据库中对应的记录,放到result中。我们返回的词id类型,和系统词典的保持一致,都是CPinyinTrie::TWordIdInfo。这里再解释一下,用户词典的最大容量为什么是6万多个。这个就是由于CPinyinTrie::TWordIdInfo的m_id的长度决定的,是2^18-1。

我们按照sqlite的限制,小心翼翼地组织好查询的where子句,查询,然后迭代结果,填充results,最后返回。

CUserDict::operator [] (wid):

这个方法也相对简单,就是返回wid对应的字符串。因为sqlite只支持utf8和utf16,而且utf16还要使用一套不同的接口,所以我们还是使用utf8作为数据库内的字符串编码。而sunpinyin-ime要求的是ucs-4编码的字符串,所以还要进行一个编码转换。同时把(wid, wstr)这个pair加入到m_dict中。wstr是动态分配的,由m_dict在析构时释放。这里还利用了这样一个事实,std::basic_string是COW的。我们在m_dict中保存的其实是wstr的拷贝,不过这个拷贝和wstr共享同一个C string的buffer。

接下来,我会介绍SunPinyin2中另一个比较复杂和tricky的部分——用户配置(imi_options.h),这一部分到目前还没有完全定型,非常欢迎大家多提宝贵意见。