Lustre
/Scade
的形式化语义的基础为同步KPN
(Synchronous Kahn networks, ICFP’96)(注1),同步KPN的思想来源可追溯至Kahn进程网络 KPN (1974)。KPN对Lustre/Scade等同步数据流语言的形式化语义起到了重要的影响,以至于
- 在 SYNCHRON 2022 Pouzet 回顾同步数据流语言历史的主题演讲中(注2),在叙述Lustre理念时首先讨论了KPN。
- 在 KPN 发表50周年时,INRIA为KPN举办了纪念主题研讨。(注3)
在本篇中,将介绍KPN,及其与Lustre/Scade 同部数据流语言的形式化语义之间的关系。
注1:参考 Synchron 2006 - Synchronous Kahn Networks (ten years later) - Marc Pouzet
注2:SYNCRHON 2022, A Brief History of Synchronous Programming.csdn.net
注3:Kahn networks, 50 years later - Marc Pouzet - Dec.16, 2024
KPN 介绍
Kahn进程网络(Kahn Process Network, KPN)是由Gilles Kahn于1974年提出的并行计算模型。其核心由若干确定性顺序进程构成,这些进程通过容量无限制的FIFO通道进行通信。每个进程从输入通道读取数据(Token),并向输出通道写入Token。在这之中,较为关键的规则在于1)读取阻塞。若通道为空,读取操作将被阻塞直至数据可用;2)写入无阻塞。假设通道容量没有边界,写入操作永不阻塞。这些规则保证了系统行为的时序无关性,即无论进程调度顺序如何,只要给定相同的输入流,系统必然产生唯一的输出流。
KPN中,每个进程是作用于数据流的纯函数(Pure Function):对相同的输入流,必然生成相同的输出流。
通道能够承载的Token流是无限的。所有通道历史数据构成的集合,在prefix关系下形成一个完全偏序(Complete Partial Order, CPO):若某段历史是另一段历史的初始部分(前缀),则前者在偏序中"小于"后者。其中,空历史(无数据)是最小元(记为⊥)。
通过将数据流建模为CPO,每个进程可被抽象为Scott连续函数,即满足:
- 单调性:若输入历史增长(如追加更多数据),输出历史不会"缩短"(保持前缀顺序);
- 极限保持性:保留递增链(如逐步扩展的输入序列)的极限结果。
作为连续函数的进程
每个 KPN(Kahn 进程网络)进程都是一个顺序程序,它完整地处理token,并且产生token。从指称语义(denotational semantics)的角度来看,一个拥有 m 条输入通道和 n 条输出通道的进程可以表示为一个函数
f : (Σ^* ∪ Σ^ω)^m → (Σ^* ∪ Σ^ω)^n
将每个输入历史元组映射为输出历史元组。(其中 Σ^* 表示有限字符串,Σ^ω 表示无限流。)由于进程只能向其输出追加Token,并且必须保持Token的顺序,因此 f 是单调的:如果输入增长(某条历史被延长),则输出不可能收缩或重排。实际上,Kahn 最初的模型假设这些函数是 Scott-连续的:既单调,又能保持前缀链的极限。
在实践中,通常将进程视为代码形式。例如
process Add:
loop:
a = read(channel1)
b = read(channel2)
write(a + b, output)
这个 Add 进程会不断地从 channel1 和 channel2 读入两个数字,并输出它们的和。这样的循环定义了一个作用于流(streams)的连续函数:每个输出token只依赖于输入的有限前缀。连续性是保证良好定义语义(即不动点存在性)的关键性质。
网络方程与不动点语义
一个完整的 KPN 由进程集 P 和通道集 S 构成,后者被划分为外部输入 I、内部通道 C 和外部输出 O(即 S = I ∪ C ∪ O,且三者两两不交)。每个进程 p ∈ P 都有指定的输入通道 Iₚ ⊆ S 和输出通道 Oₚ ⊆ S,并对应一个连续函数
fₚ : H(Iₚ) → H(Oₚ).
为了描述整个网络的行为,我们写下网络方程:在每条通道 c 上的历史必须等于其生产者所产生的历史。换言之,对于任意全局历史 h ∈ H(I ∪ C ∪ O),对每个进程 p,都有
h|_{Oₚ} = fₚ( h|_{Iₚ} ).
这里 h|{Iₚ} 表示将全局历史 h 限制到 p 的输入通道,h|{Oₚ} 类似地表示对输出通道的限制。我们还要求 h|_I = i ,i 是给定的外部输入历史。以上便是一组递归方程。
Kahn 证明,在连续性假设下,这个方程组对于给定的输入 i 存在且仅存在一个最小的因果解:即对于所有满足方程的全局历史 h,存在最小的那一个。具体地,可以通过 Kleene 迭代来构造:从空历史(⊥)开始,不断应用各进程来扩展历史,最后取极限。根据连续性和 Kleene 不动点定理,该极限良好定义,且即为网络的指称语义。
换句话说,整个网络定义了一个函数
F : H(I) → H(O)
将每个输入流元组 i 映射到最小解 h 在输出通道上的部分 h|_O。这个 F(i) 即为网络方程组在输入 i 下的最小不动点。任何其他解要么违反因果性,要么在前缀序上更大。
KPN 的语义即其进程方程组的最小不动点,它为每个输入流唯一地确定了一个(确定性的)输出流。
确定性与组合性
由于每个进程都是作用于流的纯函数,且网络语义是唯一的不动点,KPN性质上是确定性的:其运行结果不依赖于调度或时间安排。无论进程以何种顺序执行,最终得到的流都是相同的。这一性质可称为 Kahn 原理。实际上,网络的任何执行历史,即进程步骤的某种交错,都会收敛到那个最小不动点解,且所有此类执行都是一致的。
KPN 的语义也是可组合的:整个网络可被视为单个进程。在实践中,可以分层构建大型网络。如果已知两个子网络的输入/输出函数,将它们连接起来就会得到一个新的连续函数(即复合不动点)。更形式地说,一个 KPN 可以作为更大 KPN 的组件。正如 Geilen 和 Basten 所指出的:“所给出的 KPN 语义具有组合性:一个确定性进程网络由各个确定性进程构成,并且它本身可以用作更大网络中的一个进程。”因此,这种语义能够很好地支持模块化设计——这也是数据流语言中广泛采用 Kahn 模型的一个重要原因。
KPN与同步数据流语言的关系
KPN 的原理是许多信号处理和嵌入式系统中所使用的数据流与同步语言的基础。例如,Synchronous Dataflow (SDF)(Lee & Messerschmitt,1987)是 KPN 的一种约束,其中每个actor在一次触发中使用并产生固定数量的token。这种固定速率的约束允许进行静态调度,但其底层语义仍然是作用于流的连续函数。一般来说,任何同步数据流或基于流的语言(如 Lustre、Signal、StreamIt、Simulink)都可以被看作是以 KPN思路 定义的确定性流转换器:每个模块都是对无限数据流的纯函数。KPN 理论保证在这些条件下,整体模型具有良好定义的、独立于实现顺序的确定性行为。
在 SDF 及类似语言中,知道每个Actor都是流上的连续函数,就意味着整个网络具有唯一的语义(唯一的无限数据流解)。即便在具有全局时钟的完全同步语言(如 Lustre)中,基于流(通常是每个时钟周期一个值)指定方程的思想也是类似的:编译器或解释器通过求解流方程上的不动点来实现,依赖于确定性和连续性。
因此,Kahn 过程网络为许多现代流式/数据流语言提供了数学基础。它们采用完备偏序集(CPO)和最小不动点的方法,确保复杂的滤波、变换和反馈循环网络能生成唯一且可组合的语义。可以基于此确定性本质来推理功能行为(如无死锁、有界缓冲区等)。