本节主要涉及kaldi的代码组织,依赖的结构,以及修改代码,调试代码。

通用基础类库

src目录下的base和util是kaldi里面最基础的两个目录,几乎每一个kaldi的程序都依赖于这两个目录,查看base/kaldi-common.h, kaldi-common.h这个头文件中包含了base这个目录下的除了io-funcs-inl.h下的其他所有的头文件以及系统库,其中io-funcs-inl.h则被io-funcs.h包含,而io-funcs.h这包含在kaldi-common.h,可以说kaldi-common.h中包含了所有的头文件。我们可以通过头文件名知道对应的功能,比如日志记录,类型定义,数学库等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//kaldi-common.h

#ifndef KALDI_BASE_KALDI_COMMON_H_
#define KALDI_BASE_KALDI_COMMON_H_ 1

#include <cstddef>
#include <cstdlib>
#include <cstring> // C string stuff like strcpy
#include <string>
#include <sstream>
#include <stdexcept>
#include <cassert>
#include <vector>
#include <iostream>
#include <fstream>

#include "base/kaldi-utils.h"
#include "base/kaldi-error.h"
#include "base/kaldi-types.h"
#include "base/io-funcs.h"
#include "base/kaldi-math.h"
#include "base/timer.h"

#endif // KALDI_BASE_KALDI_COMMON_H_

util目录是另一个基础的目录,在util/common-utils.h中包含更多的头文件,包括前面的base下的kaldi-common.h,util目录下主要包括命令行的解析,I/O的处理,文件的读写。
之所以将基础类库的一部分分离到base中去,主要是为了最小化matrix的依赖,而且base目录本身就很有用,或者说被其他目录依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#common-utils.sh
#ifndef KALDI_UTIL_COMMON_UTILS_H_
#define KALDI_UTIL_COMMON_UTILS_H_

#include "base/kaldi-common.h"
#include "util/parse-options.h"
#include "util/kaldi-io.h"
#include "util/simple-io-funcs.h"
#include "util/kaldi-holder.h"
#include "util/kaldi-table.h"
#include "util/table-types.h"
#include "util/text-utils.h"

#endif // KALDI_UTIL_COMMON_UTILS_H_

最后一个比较基础的类库,则是matrix, kaldi中的matrxi是在BLAS和LPACK库上的进一步封装,或者说是包装器(wrapper).查看src下的matrix/matrix-lib.h 可以看到matrix-lib.h这个头文件中主要是包含其他的头文件依赖,提供了矩阵库中各种内容的概述, 从文件名可以知道其对应的功能,包括向量,矩阵,对阵矩阵,三角阵,fft变换,压缩矩阵,稀疏矩阵,以及涉及到矩阵的相关操作函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//matrix-lib.h

#ifndef KALDI_MATRIX_MATRIX_LIB_H_
#define KALDI_MATRIX_MATRIX_LIB_H_

#include "base/kaldi-common.h"
#include "matrix/kaldi-vector.h"
#include "matrix/kaldi-matrix.h"
#include "matrix/sp-matrix.h"
#include "matrix/tp-matrix.h"
#include "matrix/matrix-functions.h"
#include "matrix/srfft.h"
#include "matrix/compressed-matrix.h"
#include "matrix/sparse-matrix.h"
#include "matrix/optimization.h"

#endif

sp-matrix.h和tp-matrix.h分别与对阵矩阵和三角阵相关,kaldi-matrix.h中对MatrixBase进行定义。
上述了的base, util, matrix目录可以说是kaldi的所有程序中最基础的,或者说大部分程序都依赖于这三个目录中的代码。

修改和调试代码

这里以修改matrix下的代码为例,在matrix-lib-test.cc中添加一个测试函数,这是一个测试程序。这个测试函数给一个向量加上一个常数与向量的乘积。
在MatrixUnitTests函数上面添加UnitTestAddVec函数,UnitTestAddVec函数,是我们将要添加的函数。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<class Real>
void UnitTestAddVec() {
// note: Real will be float or double when instantiated.
int32 dim = 1 + Rand() % 10;
Vector<Real> v(dim), w(dim); // two vectors the same size.
v.SetRandn();
w.SetRandn();
Vector<Real> w2(w); // w2 is a copy of w.
Real f = RandGauss();
w.AddVec(f, v); // w <-- w + f v
for (int32 i = 0; i < dim; i++) {
Real a = w(i), b = f * w2(i) + v(i);
AssertEqual(a, b); // will crash if not equal to within
// a tolerance.
}
}

执行make test 生成测试程序matrxi-lib-test并测试,这一步会提示错误。查看matrix-lib-test.testlog可以看到具体的错误。

1
2
3
4
5
6
7
8
9
10
ASSERTION_FAILED ([5.5.333~1-e9223]:AssertEqual():base/kaldi-math.h:278) Assertion failed: (ApproxEqual(a, b, relative_tolerance))

[ Stack-Trace: ]
kaldi::MessageLogger::LogMessage() const
kaldi::KaldiAssertFailure_(char const*, char const*, int, char const*)
void kaldi::UnitTestAddVec<float>()
./matrix-lib-test() [0x451d5a]
main
__libc_start_main
_start

直接执行./matrix-lib-test也会报错,因为v和w是随机生成的,肯定不会相等。
现在执行gdb调试,这里补充一下gdb调试的知识。
(1) 启动调试

1
gdb program    //program指自己的程序

(2) gdb的命令列表

命令 命令缩写 命令说明
list l 显示多行源代码
break b 设置断点,比如b 10, 在第10行设置断点
info 查看某个值,比如info break
delete 删除某个值,比如delete breakpoint, 删除某个断点
disable disable breakpoint,禁用某个断点
run r 运行程序
display disp 追踪查看某个变量
step s 执行下一条语句,,如果该语句为函数调用,则进入函数执行其中的第一条语句
next n 执行下一条语句,如果该语句为函数调用,不会进入函数内部执行(即不会一步步地调试函数内部语句)
finish 如果已经进入了某函数,而想退出该函数返回到它的调用函数中,可使用命令finish
print p 打印内部变量值
continue c 继续程序的运行,直到遇到下一个断点
watch 监视某个变量值得变化
backtrace bt 查看函数调用的堆栈信息
up 上移栈帧
quit q 退出gdb
whatis 查看变量类型

调试matrix-lib-test程序,

1
gdb ./matrix-lib-test

输入r运行程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(gdb) r
Starting program: /home/cca01/hdd190404/luoxj/kaldi/src/matrix/matrix-lib-test
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
ASSERTION_FAILED ([5.5.333~1-e9223]:AssertEqual():base/kaldi-math.h:278) Assertion failed: (ApproxEqual(a, b, relative_tolerance))

[ Stack-Trace: ]
kaldi::MessageLogger::LogMessage() const
kaldi::KaldiAssertFailure_(char const*, char const*, int, char const*)
void kaldi::UnitTestAddVec<float>()
/home/cca01/hdd190404/luoxj/kaldi/src/matrix/matrix-lib-test() [0x451d5a]
main
__libc_start_main
_start


Program received signal SIGABRT, Aborted.
0x00007ffff61a9428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.

输入bt查看堆栈信息

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) bt
#0 0x00007ffff61a9428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007ffff61ab02a in __GI_abort () at abort.c:89
#2 0x00007ffff7933a4c in kaldi::KaldiAssertFailure_ (
func=func@entry=0x462e58 <kaldi::AssertEqual(float, float, float)::__func__> "AssertEqual",
file=file@entry=0x4603bb "../base/kaldi-math.h", line=line@entry=278,
cond_str=cond_str@entry=0x460f28 "ApproxEqual(a, b, relative_tolerance)") at kaldi-error.cc:209
#3 0x000000000045c5d4 in kaldi::AssertEqual (relative_tolerance=0.00100000005, b=<optimized out>, a=<optimized out>)
at ../base/kaldi-math.h:278
#4 kaldi::UnitTestAddVec<float> () at matrix-lib-test.cc:4602
#5 0x0000000000451d5a in kaldi::MatrixUnitTest<float> (full_test=full_test@entry=false) at matrix-lib-test.cc:4608
#6 0x000000000045c11d in main () at matrix-lib-test.cc:4766

输入up,将栈帧一层一层往上移,直到到达上述函数的内部

1
2
3
4
(gdb) up
#3 0x000000000045c5d4 in kaldi::AssertEqual (relative_tolerance=0.00100000005, b=<optimized out>, a=<optimized out>)
at ../base/kaldi-math.h:278
278 KALDI_ASSERT(ApproxEqual(a, b, relative_tolerance));

使用p打印变量的值

1
2
3
4
(gdb) p a
$1 = <optimized out>
(gdb) p b
$2 = <optimized out>

这里并没有打印出a和b的值,原因是编译是kaldi.mk使用的是g++ -O1的优化选项,同时由于使用-O1选项导致断点无效。

修改编译选项为-O0,重新执行上述步骤,即可打印出对应的值。

1
2
3
4
5
(gdb) p a
$1 = -1.39748704
(gdb) p b
$2 = -0.473557711
(gdb) q

kaldi的编码风格

1、 kaldi中类的私有变量以下划线结尾
2、kaldi的两种文件格式:scp(script) 和 ark(archive)。 .scp文件是key到文件或者管道的映射,.ark文件是数据文件,由key和value组成。kaldi的工具需要指明输入输出的文件类型,告诉kaldi怎么去读这些文件。格式如下:

wspecifier meaning
ark:foo.ark Write to archive “foo.ark”
scp:foo.scp Write to files using mapping in fdo.scp
ark:- Write archive to stdout
ark,t: | gzip -c > foo.gz Write text-form archive to foo.gz
ark,t:- Write text-form archive to stdout
ark,scp:foo.ark,foo.scp Write archive and scp file, scp file specifying offsets into that archive, like an index
rspecifier meaning
ark:foo.ark Read from archive foo.ark
scp:foo.scp Read as specified in foo.scp
ark:- Read archive from stdin
ark:gunzip -c foo.gz | Read archive from foo.gz
ark,s,cs:- Read archive(sorted) from stdin, ‘s’ asserts archive is sorted. ‘s’ asserts it will be called in sorted order