0%

如今越来越多地人开始本地部署大模型,然而大模型的本地部署需要大量的硬件资源,而这些硬件资源的利用率往往非常低。而另一边公开的模型服务经常会由于资源不足导致无法处理海量请求。因此一个很自然的想法是设计一个模型服务的分发平台,个人用户可以将私有部署的模型接入到平台中,通过执行推理任务来赚取收益,而平台自身不处理请求,而是单纯地进行任务分发。通过这种方式既可以对外提供一个高可用性和高扩展性的模型服务,也能够有效整合私有模型的硬件资源。

正如BT网络中的Free-riding Attack,这个模型服务分发平台也会面临相同的问题:用户能够很轻易地伪造推理结果来赚取收益。有一些直观的方法去校验推理结果,但它们都会有严重的缺陷:
方法一:平台对下发的推理任务进行采样,执行样本中的任务并将自身结果与用户结果进行比较,以判断用户的可信度
缺陷:用户伪造结果的成本极低,而平台对结果进行校验的成本极高。当样本量大到一定程度时,大量计算需要平台进行,失去了推理任务分发的意义
方法二:将任务同时分发给多个用户,通过投票等共识算法决定正确结果
缺陷:无法抵御女巫攻击,可以零成本地伪造大量用户来上传错误结果并赚取收益

下文将会介绍一种高效的推理结果校验算法,它解决了上述方法的缺陷,能够作为模型服务分发平台的基石:

校验算法的输入需要原始的模型参数、模型输入、每层的输出,输出为true或false表示输出是否能够匹配模型参数。
校验算法的核心是从所有层的输出向量中随机选出c个元素进行校验。对于一个特定元素a,只需要获取上一层的输出和该层参数矩阵对应于该元素的列向量,若这两个向量的点积与该元素的值一致,则认为校验成功。

  1. 部署在虚拟tap网卡上

  2. 部署在VF直通网卡上

  3. kni设备配置

  4. pdump无法抓包分析

记录Raft算法中的细节:

leader选举随机超时机制

大于两个candidate参与选举时可能导致选票分散,没有candidate获得大多数选票。condidate上会出现选举超时的情况,candidate会增加自己的term并开启新一轮选举。

如果每个candidate的超时时间一致,新一轮选举依旧会出现投票分散的情况,因此每个candidate的超时时间会在一个范围内随机选取。

日志匹配性

不同节点上的日志条目若拥有相同的term和index,那该条目及其之前的条目都是一致的。

首先给定的term和index的条目只会由唯一确定的leader下发,且leader不会覆盖或删除自己的条目,因此该条目在各节点上都一致。此外下发请求会包含leader中上一个条目的term和index,只有其与follower本地的上一个条目一致时才会被接收,因此之前的条目也都是一致的。

通过比较term和index,follower发现本地条目与下发请求不一致时会拒绝请求。leader会维护每个follower的nextIndex,下发请求被拒绝时会回退nextIndex,并下发上一个条目,重复此过程直到某个条目被follower接收。follower接收后会使用下发的条目覆盖本地条目,因此系统能够自动收敛。

日志提交方案

假设先前term中有一条目已在多数节点上被存储,若leader在得知此信息后将该条目提交,仍有可能出现该提交条目未来被覆盖的情况,导致不同节点的执行结果不一致。

为保证安全性,Raft只会提交当前term中被多数节点存储的term,并利用日志匹配性推断先前的条目均被多数节点存储。该策略牺牲了一定的实时性,推迟了先前term中条目的提交时间。

背景

使用 OSPF/ECMP 组织多个 DPVS 节点,不同节点上的 RS 创建顺序可能不一致,DPVS 一致性哈希在不同的节点能否将流量路由到相同的 RS?当有RS创建和删除时,流量的目的RS是否保持一致?

结论

假设已有 n 个 $RS$,$RS_1,…RS_n$,权重分别为 $w_1,…,w_n$。

  1. 一致性哈希基于红黑树,根据地址+端口组织RS。虽然插入顺序不同会导致底层红黑树结构不同,但相同源IP的流量会路由到相同的RS。
  2. $RSi$被选中的概率为$\frac{w_i}{\sum{i}^{n}wi}$。加入权重为$w{new}$的$RS{new}$时,会有$\frac{w{new}}{\sum{i}^{n}w_i+w{new}}$比例的流量目的$RS$会变为$RS_{new}$,其余流量目的$RS$不变;删除$RS_i$时,目的为$RS_i$的流量会根据权重比例分配到其他$RS$,其余流量目的$RS$不变;$RS$更新时,会先删除再加入。

原理

DPVS版本的ipvsadm中,一致性哈希的哈希目标默认使用源IP。此外可以通过 -Y 参数指定哈希目标为 quic 中的 qid。

哈希函数默认使用md5算法,并将结果转化为unsigned long,因此槽位有$2^{64}$个,并且在哈希冲突时不进行插入操作。此外使用红黑树组织哈希节点,节点键值为哈希结果。

RS 加入

将RS的地址+端口作为哈希节点的标识iden,根据RS权重生成哈希节点的副本数量replicas,创建哈希节点。其中副本数量会乘上常量REPLICA,具体是160。由于副本的键值是随机生成的,副本量大时 其分布会更为均匀,减少了极端情况出现的可能性。因此增加副本数量是通过牺牲少了时间以保证结果的均匀性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int dp_vs_conhash_add_dest(struct dp_vs_service *svc,
struct dp_vs_dest *dest)
{
...

p_node = &(p_conhash_node->node);
inet_ntop(dest->af, &dest->addr, addr, sizeof(addr));
snprintf(iden, sizeof(iden), "%s%d", addr, dest->port);
conhash_set_node(p_node, iden, weight / weight_gcd * REPLICA);

ret = conhash_add_node(p_sched_data->conhash, p_node);
if (ret < 0) {
RTE_LOG(ERR, SERVICE, "%s: conhash_add_node failed\n", __func__);
rte_free(p_conhash_node);
return EDPVS_INVAL;
}

}

创建哈希节点时,会在红黑树上创建副本数量个rbnode。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void __conhash_add_replicas(struct conhash_s *conhash, struct node_s *node)
{
...
for(i = 0; i < node->replicas; i++)
{
/* calc hash value of all virtual nodes */
__conhash_node2string(node, i, buff, &len);
hash = conhash->cb_hashfunc(buff);
/* add virtual node, check duplication */
if(util_rbtree_search(&(conhash->vnode_tree), hash) == NULL)
{
rbnode = __conhash_get_rbnode(node, hash);
if(rbnode != NULL)
{
util_rbtree_insert(&(conhash->vnode_tree), rbnode);
conhash->ivnodes++;
}
}
}
}

rbnode的哈希值从哈希节点的标识+副本编号计算得来。
1
2
3
4
5
6
7
8
void __conhash_node2string(const struct node_s *node, u_int replica_idx, char buf[128], u_int *len)
{
#if (defined (WIN32) || defined (__WIN32))
_snprintf_s(buf, 127, _TRUNCATE, "%s-%03d", node->iden, replica_idx);
#else
snprintf(buf, 127, "%s-%03d", node->iden, replica_idx);
#endif
}

RS 查找

直接对流量的源IP做哈希,哈希值为查找的key,在红黑树中查找rbnode,返回rbnode对应的RS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static inline struct dp_vs_dest *
dp_vs_conhash_get(struct dp_vs_service *svc, struct conhash_s *conhash,
const struct rte_mbuf *mbuf)
{
...

if (svc->flags & DP_VS_SVC_F_QID_HASH) {

...

} else if (svc->flags & DP_VS_SVC_F_SIP_HASH) {
if (EDPVS_OK == get_sip_hash_target(svc->af, mbuf, &addr_fold)) {
snprintf(str, sizeof(str), "%u", addr_fold);
} else {
return NULL;
}
} else {
RTE_LOG(ERR, IPVS, "%s: invalid hash target.\n", __func__);
return NULL;
}

node = conhash_lookup(conhash, str);
return node == NULL? NULL: node->data;
}

const struct node_s* conhash_lookup(const struct conhash_s *conhash, const char *object)
{
...
/* calc hash value */
hash = conhash->cb_hashfunc(object);

rbnode = util_rbtree_lookup((util_rbtree_t *)&(conhash->vnode_tree), hash);
if(rbnode != NULL)
{
struct virtual_node_s *vnode = rbnode->data;
return vnode->node;
}
return NULL;
}

具体会返回红黑树中大于等于key的最小rbnode,如果key小于所有的rbnode则返回最小的rbnode。由于rbnode的哈希值与源IP的哈希值无任何联系,因此查找的key就是以源IP做种子生成的随机数。相对地,可以认为每个rbnode占据一段连续哈希槽,RS拥有其所有rbnode的哈希槽,流量的目的RS就是源IP哈希值对应槽位的RS。

RS删除时,其rbnode的哈希槽会合并到前一个rbnode上,由于每个RS对应rbnode的分布是完全随机的,因此被删除RS的哈希槽会按权重比例随机并入其他RS的哈希槽中。而其余哈希槽对应的RS没有变动,因此这部分流量的目的RS不会改变。类似地,RS插入时会按权重比例抢占其他RS的哈希槽,且其余未为被抢占的哈希槽的RS不会变动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
util_rbtree_node_t* util_rbtree_lookup(util_rbtree_t *rbtree, long key)
{
if((rbtree != NULL) && !util_rbtree_isempty(rbtree))
{
util_rbtree_node_t *node = NULL;
util_rbtree_node_t *temp = rbtree->root;
util_rbtree_node_t *null = _NULL(rbtree);
while(temp != null)
{
if(key <= temp->key)
{
node = temp; /* update node */
temp = temp->left;
}
else if(key > temp->key)
{
temp = temp->right;
}
}
/* if node==NULL return the minimum node */
return ((node != NULL) ? node : util_rbtree_min(rbtree));
}
return NULL;
}