Yuzhe's Blog

yuzhes

不要提前执行 Python:把逻辑调用与物理工作拆开

Pysolate 的 Prepare–Linearize–Materialize 模型,以及它为何比“边生成边执行”更通用

一段程序规定了应该发生什么、在什么逻辑位置发生;它并不总是规定每一份物理工作必须到那个位置才开始。

Pysolate 想利用的,正是这两者之间的缝隙。

在由模型生成 Python、再调用远程工具、数据集、文件系统或其他 Host capability 的程序里,最显眼的性能问题往往不是本地的 a + b,而是大量高延迟边界操作:一次模型调用可能需要十几秒,一次搜索可能需要数秒,一次数据加载可能需要更久。最直观的优化方案是“代码来一行就执行一行”:模型刚生成 search(...),运行时立刻执行它;等后续代码继续生成,再接着运行。

这条路很诱人,也很危险。

一段尚未结束的 Python source 并不是一个已经成立的程序。它后面可能出现语法错误,可能把前面的调用放进尚未闭合的控制流,可能改变参数来源,可能在 try/finally 中重新定义异常行为,也可能产生不应提前发布的副作用。更根本地说,一旦 Host 开始执行任意 Python 前缀,它就必须维护部分解释器状态、部分控制栈、异常边界、对象身份、可变堆和后续 continuation。优化器最终会变成第二个 Python runtime,或者把“生成代码”悄悄改造成一种新的流式语言。

Pysolate 选择另一条路:

  1. 流式阶段不执行 Python。
  2. 完整 source seal 后,仍只进行一次真实、同步、fresh 的 CPython 执行。
  3. Host 可以根据已出现的 source facts,提前开始某些外部物理工作。
  4. Python 到达原调用位置时,再决定这份提前工作能否代表当前逻辑调用。
  5. 真正需要结果时,才同步取得普通 Python value。

这不是“让 Python 变成异步语言”,而是:

把逻辑执行与物理工作分离\boxed{ \text{把逻辑执行与物理工作分离} }

本文把这套思路形式化为一个三阶段模型:

Prepare early;Linearize in place;Materialize late.\boxed{ \textbf{Prepare early;\quad Linearize in place;\quad Materialize late.} }

简称 PLM:Prepare–Linearize–Materialize。

需要先说明:下文是一套面向 Pysolate 的设计模型和可实现方案,不表示当前实现已经覆盖文中所有优化。它的价值首先在于给现有的 source-time pre-dispatch、Host-side pending work、prepared input、缓存和未来的 AST 调度提供一个统一而可证明的语义框架。


一、为什么不尝试“一行一行执行”

1. 不完整的 source 不是一个安全的执行边界

假设模型已经生成:

result = send_email(address, body)

但后面还没有生成完。之后可能是:

if dry_run:
    ...

也可能是:

raise ValueError("do not send")

还可能发现整个函数最终存在语法错误。仅凭前缀,无法知道这个调用在完整程序中是否可达、是否受某个异常边界保护、是否应该发生。

对只读调用,这个问题看起来轻一些:

data = read_dataset("sales.csv")

但即使它没有写外部世界,它也可能:

所以,“看起来没有副作用”并不等于“随时执行都相同”。

2. 部分执行会把 Host 推向一个不干净的位置

若 Host 真正执行 Python 前缀,它需要回答:

这些都不是小的调度问题,而是在重新定义 Python 的执行语义。

3. 更好的哲学:不要移动“程序发生了什么”,只移动“完成它所需的物理工作”

考虑:

x = remote_read(key)

这行代码至少包含两件不同的事:

  1. 逻辑事件:程序在这里调用 remote_read(key),在这里获得值或抛出异常;
  2. 物理工作:建立连接、认证、排队、远程计算、传输、反序列化。

传统同步执行把两者绑在同一位置。Pysolate 的核心判断是:

逻辑调用位置必须保留,但有些物理工作不必等到那里才开始。

因此,优化不是:

提前执行一段 Python

而是:

提前生产一个可能在未来被逻辑调用采用的物理候选结果

这一区分是全文最重要的心智模型。


二、两条时间轴:逻辑顺序与物理时间

程序里有两种完全不同的“先后”。

1. 逻辑程序点

记为:

ℓ∈L.\ell \in \mathbb L.

它表示源程序和 CPython 控制流中的位置。例如:

a = A()
x = f(a)
b = B(x)

逻辑上一定有:

ℓA<ℓf<ℓB.\ell_A < \ell_f < \ell_B.

这条顺序描述 Python 的语义:B 是否存在、它的参数是什么、异常如何传播,都由这条逻辑轴决定。

2. 物理墙钟时间

记为:

p∈T.p \in \mathbb T.

它描述:

Pysolate 允许某个逻辑位于 ℓH\ell_H 的操作,其物理准备在更早的时间 pPp_P 开始:

pP<pL,p_P < p_L,

其中 pLp_L 是 CPython 到达原逻辑调用点的物理时间。

关键不变量是:

ℓH 不移动;只有物理工作在 p 轴上移动。\boxed{ \ell_H\ \text{不移动;只有物理工作在}\ p\ \text{轴上移动。} }

可以把它画成:

逻辑程序轴

ℓ0 -------- ℓ1 -------- ℓ2 -------- ℓ3
             H_A                     H_B
              │                       │
              │ 原始语义位置          │
              │                       │


物理时间轴

p0 -- prepare A -- prepare B -------- linearize A -- materialize A -- ...

这就是“logical ownership / physical work separation”的最简数学版本。


三、隐藏世界状态:为什么 read-only 仍然依赖时间

一个 Host call 通常被写成:

H(a),H(a),

其中 aa 是显式参数。但这在语义上并不完整。真实操作还依赖一个程序没有显式写出的全局环境:

Θp=(Wp, Ap, Cp, Qp,…).\Theta_p = \left( W_p,\ A_p,\ C_p,\ Q_p,\ldots \right).

其中:

因此,一个 Host operation 应写为:

H(a;Θp)\boxed{ H(a;\Theta_p) }

而不是只有 H(a)H(a)。

1. “只读”只是效果属性

stock_price("AAPL") 可以是 read-only,因为它不修改市场:

Wp′=Wp.W'_p = W_p.

但它仍可能满足:

H(a;Θp1)≠H(a;Θp2).H(a;\Theta_{p_1}) \ne H(a;\Theta_{p_2}).

所以:

read-only⇏temporally invariant.\boxed{ \text{read-only} \not\Rightarrow \text{temporally invariant}. }

这一区分会统一解释缓存、预取、版本验证和 snapshot。

2. 操作通常只依赖世界的一小部分

定义操作的语义依赖 footprint:

DH(a)⊆Θ.D_H(a)\subseteq \Theta.

例如:

read_git_file(commit_hash, path)

可能只依赖由 commit_hash 标识的 immutable Git object;市场、当前 cwd 和其他文件变化都与它无关。

定义两个上下文对于该调用等价:

Θp1≡H,aΘp2\Theta_{p_1} \equiv_{H,a} \Theta_{p_2}

当且仅当它们在 DH(a)D_H(a) 上不可区分。

若 HH 是确定性的,并且:

Θp1≡H,aΘp2,\Theta_{p_1} \equiv_{H,a} \Theta_{p_2},

则:

H(a;Θp1)=H(a;Θp2).H(a;\Theta_{p_1}) = H(a;\Theta_{p_2}).

这就是提前结果可以直接复用的核心条件。


四、从最简单的例子开始:issue 左移,collect 右移

考虑:

a = tool_A()       # 10s
b = tool_B()       # 20s

c = a + b

d = tool_D()       # 20s

return c + d

1. 普通同步执行

若三个调用顺序发生:

A: 10s
B: 20s
D: 20s

总时间近似:

Tsync=10+20+20=50s.T_{\mathrm{sync}} = 10+20+20 = 50\text{s}.

2. 最小 split-phase 变换

先暂时使用二阶段写法:

_hA = issue(tool_A)
_hB = issue(tool_B)
_hD = issue(tool_D)

a = collect(_hA)
b = collect(_hB)

c = a + b

d = collect(_hD)

return c + d

其中:

Python 没有 await,也没有看到 Future[int]。

三个请求几乎同时开始,因此理想总时间是:

Tsplit≈max⁡(10,20,20)=20s.T_{\mathrm{split}} \approx \max(10,20,20) = 20\text{s}.

3. 剩余等待量

对调用 ii,定义:

其剩余阻塞为:

Wi=max⁡(0,piF−piM).W_i = \max(0,p_i^F-p_i^M).

若耗时为 LiL_i,则:

piF=piI+Li,p_i^F=p_i^I+L_i,

所以:

Wi=max⁡(0,piI+Li−piM).\boxed{ W_i = \max(0,p_i^I+L_i-p_i^M). }

这直接说明:

但这只是性能关系。能不能移动,仍由语义依赖决定。


五、为什么二阶段还不够:加入 Linearize

若操作依赖会变化的 Θp\Theta_p,单纯写:

candidate = issue(H, args)
...
value = collect(candidate)

会产生一个问题:

这个调用到底读取的是 issue 时的世界,原调用位置的世界,还是 collect 时的世界?

例如:

price = stock_price("AAPL")
pure_local_work()
return price

提前请求可能在 pPp_P 读到 100;原调用点在 pLp_L;真正 collect 在 pMp_M。三个时间可能不同。

因此更干净的 normal form 是三阶段。

1. Prepare

candidate = prepare(H, frozen_args)

形式上:

PH(a,pP)→c.P_H(a,p_P)\rightarrow c.

它产生一个 candidate,可以包括:

pending request
candidate result
version / ETag
snapshot ID
lease
authority epoch
provider session

Prepare 允许做物理工作,但在抽象语义中必须静默:

Obs⁡(PH)=ϵ.\operatorname{Obs}(P_H)=\epsilon.

即它不能提前向 Python抛异常、发布逻辑写操作或改变 Guest-visible state。

2. Linearize

当 CPython 到达原始调用位置:

job = linearize_or_start(candidate, H, actual_args)

形式上:

LH(c,a,ΘpL)→k.L_H(c,a,\Theta_{p_L})\rightarrow k.

它决定:

典型规则:

LH(c,a,ΘpL)={adopt⁡(c),Valid⁡H(c,a,ΘpL)start⁡(H,a,ΘpL),otherwise.L_H(c,a,\Theta_{p_L}) = \begin{cases} \operatorname{adopt}(c), & \operatorname{Valid}_H(c,a,\Theta_{p_L}) \\[4pt] \operatorname{start}(H,a,\Theta_{p_L}), & \text{otherwise}. \end{cases}

Linearize 固定在原调用的逻辑位置。

3. Materialize

value = materialize(job)

形式上:

MH(k,pM)→r.M_H(k,p_M)\rightarrow r.

它:

所以完整原则是:

P 尽早,L 原位,M 尽晚。\boxed{ P\ \text{尽早,}\quad L\ \text{原位,}\quad M\ \text{尽晚。} }

六、核心正确性契约

对原始同步操作:

H(a;ΘpL),H(a;\Theta_{p_L}),

PLM 协议必须满足:

MH(LH(PH(a,pP),a,ΘpL),pM)≡H(a;ΘpL).\boxed{ M_H \left( L_H \left( P_H(a,p_P), a, \Theta_{p_L} \right), p_M \right) \equiv H(a;\Theta_{p_L}). }

其中:

pP≤pL≤pM.p_P\le p_L\le p_M.

直观地说:

无论物理准备多早开始,只要系统在原逻辑调用点验证、采用或重新启动正确的操作,并且只在合法位置交付结果,Guest 观察到的行为就应等价于在该逻辑点进行一次普通同步调用。

如果 HH 是 nondeterministic relation,则不要求结果等于某个唯一值,而要求:

Outcome⁡PLM∈⟦H⟧(a,ΘpL),\operatorname{Outcome}_{PLM} \in \llbracket H\rrbracket(a,\Theta_{p_L}),

即 PLM 产生的结果必须是原操作在该上下文中允许的结果之一。

Candidate validator 的 soundness

设 candidate 为:

c=(r^,ν),c=(\hat r,\nu),

其中 ν\nu 是版本、lease 或 snapshot evidence。Validator 必须满足:

Valid⁡H(c,a,ΘpL)=true⇒r^∈⟦H⟧(a,ΘpL).\boxed{ \operatorname{Valid}_H(c,a,\Theta_{p_L})=\text{true} \Rightarrow \hat r \in \llbracket H\rrbracket(a,\Theta_{p_L}). }

它可以不 complete:有些其实有效的 candidate 被保守拒绝,只会损失性能。它不能不 sound:不能把过期候选错当成当前逻辑结果。


七、stock price:时间隐变量下的完整例子

考虑:

price = stock_price("AAPL")

情况一:provider 只支持“当前价格”

Prepare 在 pPp_P 得到:

r^=100.\hat r=100.

到 pLp_L 没有版本、snapshot 或可验证证据。此时:

Valid⁡=false.\operatorname{Valid}=\text{false}.

系统不能把 100 当成当前逻辑调用的结果,必须在 pLp_L 重新查询。

但 Prepare 仍然可能提前完成:

DNS
TLS
连接池建立
认证
provider route
request object allocation

这说明物理阶段还可以进一步细分:

PH0→PH1→⋯→LH→MH.P_H^0\rightarrow P_H^1\rightarrow\cdots\rightarrow L_H\rightarrow M_H.

越不依赖当前世界的阶段,越能向左移动。

情况二:provider 返回版本序号

Prepare 得到:

(r^=100,ν=42).(\hat r=100,\nu=42).

在 pLp_L,系统执行一个可线性化的 current-version check。若仍为 42,则 candidate 在这个逻辑点可采用;若已是 43,则重新请求。

这里 version check 是候选结果与当前世界之间的证明。

情况三:调用绑定 snapshot

price = stock_price("AAPL", snapshot=s)

此时语义是:

H("AAPL";s),H("AAPL";s),

而不是随 wall-clock 变化的 current read。只要 snapshot immutable,结果就可以:

这也说明:

Cache 与 early issue 在语义上是同一种东西:它们都是“在别的物理时间产生的候选结果”,由 linearize 判断能否代表当前逻辑调用。


八、复杂控制流:Host 不判断 branch,CPython 仍是唯一语义执行器

考虑:

user = get_user("alice")

if user["premium"]:
    score = normalize(user["score"])       # 普通 Python,3s
    recs = search(user["topic"])           # 15s

    if score > 10 and len(recs) > 3:
        quote = price(recs[0]["sku"])      # 10s
    else:
        quote = fallback("premium")        # 6s
else:
    quote = fallback("basic")              # 6s

tax = tax_table("UK")                      # 20s
return render(quote, tax)

假设:

1. Source streaming 阶段

Host 读到:

user = get_user("alice")

参数 source-closed,因此:

prepare get_user("alice")

看到:

search(user["topic"])

此时不能执行 user["topic"],因为这需要真正的 Python object 和 __getitem__ 语义。Host 只记录该 call site,不发请求。

看到:

fallback("premium")
fallback("basic")

若 contract 允许 control speculation,可以准备两个候选。

看到:

tax_table("UK")

参数 source-closed,立刻准备。

整个 streaming 阶段:

2. Sealed 后的概念性 AST 变换

_p_user = prepare_or_reuse(GET_USER, "alice")
_p_tax = prepare_or_reuse(TAX, "UK")
_p_fp = prepare_or_reuse(FALLBACK_PREMIUM, "premium")
_p_fb = prepare_or_reuse(FALLBACK_BASIC, "basic")

_j_user = linearize_or_start(
    _p_user, GET_USER, "alice"
)
user = materialize(_j_user)

if user["premium"]:
    # 现在 user 是真正的 Python dict。
    _p_search = prepare(SEARCH, user["topic"])
    _j_search = linearize_or_start(
        _p_search, SEARCH, user["topic"]
    )

    # search 在 Host 后台运行;CPython 正常做本地计算。
    score = normalize(user["score"])

    recs = materialize(_j_search)

    if score > 10 and len(recs) > 3:
        _p_price = prepare(PRICE, recs[0]["sku"])
        _j_price = linearize_or_start(
            _p_price, PRICE, recs[0]["sku"]
        )
        quote = materialize(_j_price)
    else:
        _j_fp = linearize_or_start(
            _p_fp, FALLBACK, "premium"
        )
        quote = materialize(_j_fp)
        discard(_p_fb)
else:
    _j_fb = linearize_or_start(
        _p_fb, FALLBACK, "basic"
    )
    quote = materialize(_j_fb)
    discard(_p_fp)

_j_tax = linearize_or_start(
    _p_tax, TAX_TABLE, "UK"
)
tax = materialize(_j_tax)

return render(quote, tax)

3. 这里到底发生了什么

整个执行自然形成多个 wave:

source-time prepare wave
        ↓
materialize user
        ↓
CPython 决定外层 branch
        ↓
runtime prepare search
        ↓
CPython 做独立本地工作
        ↓
materialize search
        ↓
CPython 决定内层 branch
        ↓
prepare / adopt price 或 fallback
        ↓
materialize

没有 Python scheduler,也没有 Host-side Python graph evaluator。


九、数据依赖:为什么有些 critical path 无法消除

考虑:

a = tool_A()       # 10s
x = a + 1
b = tool_B(x)      # 20s

d = tool_D()       # 20s
return b + d

Pysolate 可以在最早时准备:

A
D

但 B 的参数依赖:

x = a + 1

按设计,Host 不执行这段 Python。因此:

  1. materialize A;
  2. CPython 计算 x;
  3. 才能 prepare/linearize B。

关键路径仍是:

A(10)→Python (+1)→B(20)=30s.A(10) \rightarrow \text{Python }(+1) \rightarrow B(20) = 30\text{s}.

D 可以与这条链并行,所以总时间接近:

max⁡(30,20)=30s.\max(30,20)=30\text{s}.

这不是优化器失败,而是明确的 semantic staging barrier:

任意 Python 中间计算不会被 Host 提前求值。\boxed{ \text{任意 Python 中间计算不会被 Host 提前求值。} }

若未来希望在 A 完成的一瞬间,即使主 Python thread 正在做其他工作,也自动计算 x 并启动 B,就必须引入至少一种额外机制:

这些都不属于最小、clean 的 PLM 模型。


十、副作用:不是“能不能提前发”,而是“哪一阶段可以提前”

副作用不是一个布尔标签。一个逻辑操作往往可以拆成多个物理阶段,其中有些静默,有些不可提前。

1. 四类 operation contract

Immutable / snapshot read

读取 content hash
读取固定 Git commit
读取固定 dataset version

结果不依赖变化中的当前世界,可以完整 Prepare。

Validated read

读取有 ETag/version 的对象
读取带 snapshot token 的数据库结果

Prepare 产生候选和证据;Linearize 验证,不通过则重做。

Prepare–commit effect

例如:

send_email(to, body)

可以提前:

解析地址
生成 MIME
上传临时附件
建立连接
完成 spam / policy 检查

但真正“发送”必须在原逻辑点 commit。

形式上:

PH(a)→prepared effectP_H(a)\rightarrow \text{prepared effect} LH→claimL_H\rightarrow \text{claim} CH→publish/commit.C_H\rightarrow \text{publish/commit}.

这里 commit 通常不能向右越过其他逻辑效果。

Non-stageable

若调用本身:

则它不能提前,退化为普通同步调用。

2. Speculation 的真正条件

一个调用藏在未知 branch 中:

if cond:
    x = H()

要在 cond 未知时 Prepare,必须保证:

Prepare⁡(H)\operatorname{Prepare}(H)

与未执行一样,在逻辑上不可观察。

若最终不进入 branch:

discard(candidate)

必须满足:

Obs⁡(discard⁡)=ϵ.\operatorname{Obs}(\operatorname{discard})=\epsilon.

所以“read-only”仍不够。一次 read 可能消耗 quota、占用唯一 lease、触发审计或影响 provider 的后续行为。所有这些若对程序可观察,都属于 effect footprint。

3. Authority 也属于语义状态

提前工作不能绕过权限。

一个 candidate 至少应绑定:

logical run
capability identity
authority epoch
tool identity
normalised argument digest
source seal
dynamic occurrence

对写操作,Linearize 或 Commit 时通常需要重新检查 authority。若 capability 在 Prepare 后被 revoke,候选结果不得自动发布。

这使得:

物理工作可以早做、复用或共享;逻辑授权、发布和 mutable state 仍然属于每个 logical run。


十一、异常:Materialize 不能无条件右移

考虑:

a = H()          # 可能失败
send_email()
return a

若变成:

_p = prepare(H)
_j = linearize_or_start(_p, H)

send_email()

a = materialize(_j)
return a

当 H 失败时:

因此不等价。

1. Materialize sinking 的可交换条件

设:

M=materialize⁡(j)M=\operatorname{materialize}(j)

而 SS 是紧随其后的语句。只有当:

Obs⁡(M;S)=Obs⁡(S;M)\operatorname{Obs}(M;S) = \operatorname{Obs}(S;M)

时,才能把 MM 跨过 SS。

记为:

M⋈S.M\bowtie S.

若:

M⋈S1,…,M⋈Sn,M\bowtie S_1,\ldots,M\bowtie S_n,

则可通过相邻交换得到:

M;S1;⋯ ;Sn≡S1;⋯ ;Sn;M.M;S_1;\cdots;S_n \equiv S_1;\cdots;S_n;M.

2. 一个保守实现允许跨越什么

可以跨越的典型语句:

已证明 pure
不会抛异常
不读取结果变量
不观察局部变量绑定
不改变 operation 的 temporal validity
不改变 authority / transaction / cwd

必须停止的 barrier:

可观察 I/O 或外部效果
可能抛异常的未知调用
try / except / finally
with / transaction
return / yield
时间与随机读取
locals / frame introspection
线程或 signal 边界
结果的第一次 strict use

因此,第一版实现完全可以:

只做 Prepare 左移,Materialize 保持原 call site。

大部分并发收益已经存在。Materialize sinking 是更强、更受限的后续优化。


十二、其他工作如何理解这类问题

Pysolate 并不是在真空中出现。它与 futures、dataflow、lazy evaluation、async/await、partial evaluation、speculation 和缓存都有亲缘关系。但它把边界放在了不同的位置。

1. Futures / Promises:让“尚未完成的值”进入语言

Multilisp 的 future 允许一个表达式并发求值,后续在需要具体值时隐式或显式等待;Liskov 与 Shrira 的 promises 则面向异步远程过程调用,把请求发出与取得返回值分离。[1][2]

典型心智模型是:

a: Future[int] = async_A()
b: Future[int] = async_B()
c = touch(a) + touch(b)

它的核心对象是:

Future[T].Future[T].

Pysolate 的区别是:

Pysolate 借用了“调用与等待分离”,但把 Future 限制为编译器和 Host 的内部 handle,而不是扩展 Guest value domain。

2. Python async / await:显式协程与 event loop

Python 原生异步模型要求 coroutine 在 await 处挂起,把控制权交还 event loop;event loop 在 Task 等待 Future 时调度其他任务。[3]

这适合开发者主动编写异步应用:

a_task = asyncio.create_task(A())
b_task = asyncio.create_task(B())
a, b = await asyncio.gather(a_task, b_task)

Pysolate 刻意不采用这个用户模型:

换句话说,asyncio 让程序显式管理并发;PLM 让编译器移动边界工作的物理阶段。

3. APPL:Future proxy 与按需同步

APPL 是一个很接近的对照。它通过 AST transpilation 集成 Python 与 LLM calls,并使用 StringFuture、BooleanFuture 等对象延迟同步:字符串拼接可以继续组合 future,len、str 或布尔控制流等 strict context 才会 materialize。APPL 的论文明确把自动并行化建立在 asynchronous semantics 和 Future objects 上。[4]

它的模型可以概括为:

调用返回 Future-like Python object
Future 参与有限的 Python 操作
在 first strict use 时同步

Pysolate 的核心差别在于:

  1. Streaming phase 不执行 Python。
  2. 不让 Future proxy 渗入普通 Python 值语义。
  3. 复杂控制流仍由 sealed 后的一次同步 CPython 决定。
  4. 提前工作必须通过 temporal、authority 和 effect contract 在原逻辑点被采用。
  5. 目标不局限于 LLM generation result,也适用于任意 Host-owned capability。

因此 APPL 更接近“带透明 Future 的 Python 扩展”,Pysolate 更接近“同步 Python 外围的 split-phase physical-work overlay”。

4. Dataflow:让整个程序成为依赖图

Dataflow runtime 通常把 computation 表示为图:节点在输入依赖满足后触发。Program Dependence Graph 则显式表示程序的数据依赖和控制依赖,可用于优化和代码移动。[5]

若把示例完全 dataflow 化:

A ─┐
   ├─ add ─┐
B ─┘       ├─ add -> result
D ─────────┘

调度器可以直接沿 critical path 执行。

Pysolate 不做完整 dataflow runtime:

因此更准确的结构是:

普通 Python program
+
Host physical-work overlay

而不是:

Python -> 完整计算图 -> Host 执行图

5. Lazy evaluation:延迟计算,而 Pysolate 提前工作

Call-by-need 延迟一个表达式的求值,直到它的值被需要,并通过 sharing 避免重复计算。Launchbury 的语义用显式 heap bindings 描述这种共享和按需求值。[6]

PLM 与 lazy evaluation 有一种镜像关系:

Lazy evaluation:
    尽晚开始计算
    在需要时求值

PLM:
    尽早开始物理工作
    在需要时才交付/实体化结果

可以把 Pysolate 称为:

physically eager, logically strict, materially deferred

即物理上积极,逻辑上仍保持原同步顺序,materialization 可以延迟。

6. Partial evaluation 与 multi-stage programming:提前执行静态程序

Partial evaluation 根据已知输入提前计算程序的静态部分,生成 residual program;multi-stage programming 则显式区分代码生成和执行阶段。[7]

Pysolate 的差别非常关键:

当然,prepared dataset folding 或把已验证输入替换为常量,与 partial evaluation 有交集。但那是额外的 source transformation,不是 PLM 正确性的必要组成部分。

7. Speculative execution 与 prefetch:提前做可能需要的工作

CPU branch speculation、compiler prefetch 和数据库预取都试图在需求确定前启动工作。PLM 与它们共享:

提前做
可能浪费
错误路径丢弃
关键路径缩短

区别在于 Pysolate 的 speculation 被 effect 和 authority contract 包围:候选结果不能因为“只是优化”就提前产生逻辑效果。

8. Linearizability:给异步物理过程一个逻辑生效点

Linearizability 要求一个并发操作看起来像在 invocation 与 response 之间某个瞬间原子发生。[8]

PLM 借用这个心智模型,但场景并不完全相同:

这个中间点把“提前物理结果”重新锚定到原 Python 语义。

9. Effect systems:代码移动必须知道它会触碰什么

Effect systems 用静态信息描述表达式可能产生的 effects,从而支持安全并行化、重排和隔离推理。[9]

PLM 的工具 metadata 本质上是一种面向 Host boundary 的 effect/temporal system:

读什么
写什么
是否 immutable
是否可验证
是否可 speculation
何时检查 authority
异常是否稳定
是否支持 prepare/commit

它不需要为完整 Python 建立精确 effect type system;未知即 barrier,仍可获得保守正确性。

10. Leases、version 与缓存一致性

分布式系统中的 leases 用有期限的保证支持缓存一致性。[10] 在 PLM 中,lease 可以直接变成 candidate validity evidence:

value
version
valid_until
authority_epoch

这使“时间 tt”不再只是一个模糊风险,而成为可验证的协议条件。

11. LLM program runtimes

LMQL 把 prompt、constraint 和控制流编译成高效 inference procedure;SGLang 提供结构化 LLM program frontend,并在 runtime 侧做并行控制和 KV-cache reuse。[11][12]

这些系统主要优化 LLM program semantics 或 serving substrate。PLM 的抽象更低、更横向:


十三、为什么这套设计是广泛通用的

PLM 的适用范围不是由“工具类型”决定,而是由一个可分解契约决定:

∃PH,LH,MHs.t.MH(LH(PH(⋅)))≡H(⋅).\exists P_H,L_H,M_H \quad\text{s.t.}\quad M_H(L_H(P_H(\cdot)))\equiv H(\cdot).

只要存在这样的 factorisation,就可以优化。

1. 它统一了 pre-dispatch 与 cache

Source-time request:

现在开始,之后采用

Cache:

以前完成,现在采用

二者都是 candidate:

c=(physical outcome,validity evidence).c=(\text{physical outcome},\text{validity evidence}).

Linearize 不关心它是刚开始、已经完成,还是来自更早的缓存;只关心它是否代表当前逻辑 operation。

2. 它统一了 warm preparation 与请求执行

一个 operation 可以分成:

runtime / interpreter preparation
connection preparation
argument-independent provider setup
argument-dependent request
time-sensitive read
validation
deserialisation
publication

每层拥有不同依赖 footprint,因此能移动到不同位置。

3. 它不依赖完整静态图

即使 branch、loop 或参数只在运行时确定,CPython 到达新 frontier 后仍可发出下一批 work。无需一开始知道完整 DAG。

4. 它允许保守退化

任何证明失败:

不 Prepare
不 speculation
不 sink Materialize

直接回到:

value = synchronous_host_call(...)

所以正确性不依赖优化成功。

5. 它适合 Host-owned authority boundary

若 Guest 本来就不能直接访问网络、Git、文件或 provider,而只能通过 typed Host capability,Host boundary 天然提供:

统一拦截点
工具身份
参数序列化
effect metadata
authority check
trace / replay
cancellation
result ownership

这使 PLM 比对任意 Python library call 做猜测更现实。


十四、形式化:一个最小核心语言

不需要尝试一次证明完整 CPython。可以定义一个小语言,再把实现限制映射到 admitted subset。

1. Baseline language

表达式:

e::=v∣x∣f(e1,…,en)e ::= v \mid x \mid f(e_1,\ldots,e_n)

语句:

s::=skip⁡∣x:=e∣x:=H(e)∣s1;s2∣if⁡ e s1 s2∣return⁡ e∣raise⁡ e.s ::= \operatorname{skip} \mid x:=e \mid x:=H(e) \mid s_1;s_2 \mid \operatorname{if}\ e\ s_1\ s_2 \mid \operatorname{return}\ e \mid \operatorname{raise}\ e.

其中:

程序状态:

⟨s,σ,Θ,π⟩\langle s,\sigma,\Theta,\pi\rangle

包括:

2. PLM 扩展

加入:

c:=PH(e)c:=P_H(e) k:=LH(c,e)k:=L_H(c,e) x:=MH(k)x:=M_H(k) DH(c).D_H(c).

其中 candidate cc 与 job kk 都是 Guest 不可伪造的内部 token。

3. Labelled transitions

区分内部事件:

τint={prepare, complete, validate, cacheHit, discard}\tau_{\mathrm{int}}= \{ prepare,\ complete,\ validate,\ cacheHit,\ discard \}

和可见事件:

τvis={logicalCall, return, exception, commit, output}.\tau_{\mathrm{vis}}= \{ logicalCall,\ return,\ exception,\ commit,\ output \}.

定义:

hide⁡int(τ)\operatorname{hide}_{int}(\tau)

删除物理内部事件。

4. Trace refinement 定理

对 admitted program PP,若:

  1. Prepare 对逻辑状态 silent;
  2. validator sound;
  3. invalid candidate 在 Linearize 时 fallback;
  4. untaken candidate 可 silent discard;
  5. Materialize 返回对应 job 的结果或异常;
  6. phase movement 尊重 data/control/effect/exception/temporal dependencies;
  7. 无法证明的 site 保留 baseline call;

则:

hide⁡int(Traces⁡(T(P)))⊆Traces⁡(P).\boxed{ \operatorname{hide}_{int} \left( \operatorname{Traces}(T(P)) \right) \subseteq \operatorname{Traces}(P). }

这表示:变换程序的每个可见行为,都能由原同步程序解释。

在 deterministic、snapshot-fixed、无 real-time observation 的更强条件下,可加强为 trace equality:

hide⁡int(Traces⁡(T(P)))=Traces⁡(P).\operatorname{hide}_{int} \left( \operatorname{Traces}(T(P)) \right) = \operatorname{Traces}(P).

5. 为什么用 refinement,而不是要求相同 wall-clock

如果程序读取真实时间:

start = time.time()
...

任何让程序更快的优化都会改变它看到的时间。

同样,若 stock_price() 真正读取现实市场,优化后程序更早到达原 call site,本来就可能看到不同市场状态。

因此,不能对任意 wall-clock-observing Python 声称 lockstep equivalence。更准确的 theorem 是:

对同一外部 history,隐藏内部 Prepare 事件后,PLM 的逻辑 operation 可以在原 call site 合法 linearize,并产生 baseline semantics 允许的 trace。

若要求严格复现,则必须:


十五、证明结构

全局证明可由几个局部引理组成。

引理一:Silent Prepare insertion

若:

PHP_H

只改变 Host 私有状态 π\pi,不改变 σ,Θ\sigma,\Theta,也不产生 visible label,则插入 Prepare 是 stuttering step:

A;B≈A;PH;B.A;B \approx A;P_H;B.

引理二:Valid adoption

由 validator soundness:

ValidH(c,a,Θ)=true⇒Outcome(c)∈⟦H⟧(a,Θ).Valid_H(c,a,\Theta)=true \Rightarrow Outcome(c)\in\llbracket H\rrbracket(a,\Theta).

所以采用 candidate 不会产生 baseline 不允许的结果。

引理三:Invalid fallback

若 candidate 无效,Linearize 从当前逻辑上下文启动 canonical HH,因此直接恢复 baseline semantics。

引理四:Untaken speculation

若 Prepare 与 Discard 都 silent,未进入 branch 的 candidate 只改变 π\pi,隐藏内部事件后不可见。

引理五:Materialize commutation

若:

M⋈S,M\bowtie S,

则:

M;S≈S;M.M;S\approx S;M.

通过归纳可把 Materialize 安全地向右移动到任意连续可交换区间末端。

合成

AST transformation 可分解为有限次:

插入 Prepare
原调用替换为 Linearize + Materialize
移动 Prepare
移动 Materialize
插入 Discard

每一步保持 trace refinement;由传递性得到全程序性质。


十六、把程序变成 PLM dependence graph

优化器不需要构造完整 Python dataflow graph,只需在普通 CFG/PDG 上为每个 Host call hh 创建:

Ph→Lh→Mh.P_h\rightarrow L_h\rightarrow M_h.

1. 基础边

参数定义 ───────────────> P_h 或 L_h
P_h ───────────────────> L_h
L_h ───────────────────> M_h
M_h ───────────────────> 结果 consumers

若参数 source-time 已知,定义可指向 PhP_h;若参数只能由 CPython 产生,则只能在 LhL_h 前准备。

2. Control edge

逻辑调用必须受原 branch 控制:

condition→Lh.condition\rightarrow L_h.

若不允许 speculation:

condition→Ph.condition\rightarrow P_h.

若允许 silent speculation,可以删除到 PhP_h 的 control edge,但绝不能删除到 LhL_h 的边。

这精确表达:

physical preparation 可以跨越未知控制流;logical occurrence 不可以。

3. Effect edge

若一个语句会改变 operation 的世界 footprint、authority 或 validity,则:

effect→Lh.effect\rightarrow L_h.

若连 Prepare 都受影响,则边也指向 PhP_h。

4. Exception edge

若前一个 operation 的失败会阻止后一个逻辑调用:

Mi→Lj.M_i\rightarrow L_j.

可以提前 Prepare jj,但不能提前让 jj 产生逻辑效果。

5. Temporal order edge

两个 read 也不一定可交换:

a = current_price()
b = current_price()

若它们没有共同 snapshot,原程序要求逻辑顺序:

La→Lb.L_a\rightarrow L_b.

这说明传统 read/read independence 在带时间世界中不充分。


十七、形式化的优化目标

在理想无限资源下,固定依赖图的 latency 下界由 critical path 决定;work/span 模型用总工作量与最长依赖链刻画并行计算。[13]

PLM 的最简单调度原则:

Ph→ASAPP_h\rightarrow ASAP Lh→固定在原 logical pointL_h\rightarrow \text{固定在原 logical point} Mh→ALAPM_h\rightarrow ALAP

即:

Prepare 尽早,Linearize 原位,Materialize 在依赖允许下尽晚。

但真实系统还有:

分支命中概率
candidate 过期概率
provider 成本
rate limit
内存
并发 quota
取消成本
未知 latency

所以最优调度是 policy,而不是一个固定拓扑序。

可以定义:

π∗=arg⁡min⁡πE[Treturn+λCprovider+μCwaste+νCcontention].\pi^* = \arg\min_\pi \mathbb E[ T_{\mathrm{return}} + \lambda C_{\mathrm{provider}} + \mu C_{\mathrm{waste}} + \nu C_{\mathrm{contention}} ].

实际不必求全局最优。以下几个简单优化已经足够有效。


十八、简单而有效的优化一:Source-closed Prepare

若 streaming analyzer 看到:

tax = tax_table("UK")

并证明:

tool identity 已知
参数由 literal / sealed constant 构成
authority 已知
Prepare silent

就立刻创建:

candidate = prepare(site, TAX_TABLE, ("UK",))

final runtime 到原位置时:

job = linearize_or_start(
    candidate,
    TAX_TABLE,
    actual_args=("UK",),
)
tax = materialize(job)

必须动态检查:

source seal
site fingerprint
tool identity
actual args digest
authority epoch
logical run
temporal evidence

不匹配就 fallback。

这是一项高收益、低复杂度优化,因为 runtime guard 把大量静态证明变成“猜错只失去性能”。


十九、简单而有效的优化二:基本块内 Prepare hoisting

原代码:

a = A()
x = pure_local_1()
b = B()
y = pure_local_2()
c = C()
return combine(a, b, c, x, y)

若 A/B/C 的参数已知,变为:

_pA = prepare(A)
_pB = prepare(B)
_pC = prepare(C)

_jA = linearize_or_start(_pA, A)
a = materialize(_jA)

x = pure_local_1()

_jB = linearize_or_start(_pB, B)
b = materialize(_jB)

y = pure_local_2()

_jC = linearize_or_start(_pC, C)
c = materialize(_jC)

return combine(a, b, c, x, y)

即使 Materialize 完全不移动,三个调用也已并行。

编译器只需:

  1. A-normalize 保持 Python 左到右求值;
  2. 在基本块内向前扫描;
  3. Prepare 跨过已知不会影响参数、authority 或 effect 的语句;
  4. 遇到未知调用、异常边界或 mutation 就停止。

二十、简单而有效的优化三:局部 Materialize sinking

原代码:

a = read_immutable()
x = pure_noexcept()
y = another_pure_noexcept(x)
return use(a, y)

若 read_immutable:

total
noexcept
immutable result

则:

_p = prepare(read_immutable)
_j = linearize_or_start(_p, read_immutable)

x = pure_noexcept()
y = another_pure_noexcept(x)

a = materialize(_j)
return use(a, y)

Materialize 与本地计算 overlap。

实现上使用一个保守 commutation predicate:

can_sink(M, statement) =
    statement.pure
    and statement.noexcept
    and not reads_result
    and not observes_locals
    and not changes_context

未知即 false。


二十一、简单而有效的优化四:分支 speculation

原代码:

if cond:
    x = fallback("premium")
else:
    x = fallback("basic")

若两个调用:

Prepare silent
结果 immutable
cost 低
可取消/丢弃

则可在 cond 未知时准备二者,最终只 linearize 一边。

Expected benefit 可粗略估计为:

Uh=Preach(h)⋅Pvalid(h)⋅hiddenLatency⁡(h)−λCh−μCcontention.U_h = P_{\mathrm{reach}}(h) \cdot P_{\mathrm{valid}}(h) \cdot \operatorname{hiddenLatency}(h) - \lambda C_h - \mu C_{\mathrm{contention}}.

只在:

Uh>0U_h>0

时 speculation。

不需要复杂 ML scheduler。初版可以采用:

每个 run 最多 N 个 speculative jobs
每个 tool 最多 M 个
只允许低成本 immutable read

二十二、简单而有效的优化五:Just-in-time Prepare

加入时间 validity 后,“越早越好”不再总正确。

假设:

希望:

pP+L≤p^Lp_P+L\le \hat p_L

以便调用前完成;又希望:

p^L≤pP+L+δ\hat p_L\le p_P+L+\delta

以便调用时仍有效。

因此合适的 issue window 是:

p^L−L−δ≤pP≤p^L−L.\boxed{ \hat p_L-L-\delta \le p_P \le \hat p_L-L. }

例子:

这是一项简单但很有辨识度的 temporal scheduling 优化。


二十三、简单而有效的优化六:Demand promotion 与取消

Host job priority:

正在被 materialize 等待
    >
已进入真实 control path
    >
same-path source-time prepare
    >
未知 branch speculation

当 Python 调用:

materialize(job)

Host 把 job 提升为最高优先级。

branch 确定后,未选择候选:

discard(candidate)

尽快取消,避免 speculative work 占用关键路径的 provider quota。


二十四、简单而有效的优化七:Singleflight 与 Cache 统一

如果多个 logical runs 请求:

同一 immutable operation
同一参数
同一 authority-compatible context

Host 可以共享一份 physical work:

one physical candidate
    ├── private adoption for run A
    ├── private adoption for run B
    └── cache backing

但必须保持:

logical handle per run
private result ownership
mutable data private copy / COW
cancellation per run
authority checked independently

这正是“shared work, separate sandboxes”的一般化:

共享物理生产,不共享逻辑所有权。\boxed{ \text{共享物理生产,不共享逻辑所有权。} }

二十五、实现轮廓

1. Compiler pipeline

sealed source
    ↓
parse AST
    ↓
标记 Host capability calls
    ↓
A-normalisation
    ↓
CFG / control dependence / exception region
    ↓
def-use / mutation / effect facts
    ↓
创建 P-L-M 节点
    ↓
Prepare hoisting
    ↓
Materialize sinking
    ↓
AST rewrite
    ↓
verifier
    ↓
同步 CPython

Streaming analyzer 使用更保守的 prefix facts,只处理 source-closed site。

2. 最小 ABI

candidate = __pysolate_prepare__(
    site_token,
    tool_id,
    frozen_args,
    contract_token,
)

job = __pysolate_linearize__(
    site_token,
    tool_id,
    actual_args,
    candidate,
)

value = __pysolate_materialize__(job)

__pysolate_discard__(candidate)

实际可融合:

3. Host job state

CREATED
  ↓
PREPARING
  ├── PENDING
  ├── CANDIDATE_READY
  ├── TENTATIVE_FAILED
  └── CANCELLED

LINEARIZED
  ├── ADOPTED
  ├── VALIDATING
  └── CANONICAL_RUNNING

MATERIALIZED
  ├── RETURNED
  └── RAISED

4. Tool contract

temporal:
    IMMUTABLE
    SNAPSHOT
    VERSIONED
    LEASED
    CURRENT
    WALLCLOCK_OBSERVING

effect:
    SILENT_READ
    PREPARE_COMMIT
    NON_STAGEABLE

speculation:
    NEVER
    SAME_PATH
    BUDGETED

failure:
    STABLE
    RETRY_AT_LINEARIZE
    VALIDATE_AT_LINEARIZE

authority:
    BIND_AT_PREPARE
    RECHECK_AT_LINEARIZE
    RECHECK_AT_COMMIT

5. Verifier

至少验证:

每个 candidate/job 只能由内部 ABI 创建
Materialize 的定义支配所有结果使用
speculation 只用于允许的 contract
Prepare 参数与 runtime actual args 可校验
不跨越禁止的 exception/effect/authority region
循环动态 occurrence 不错误复用
未采用 candidate 有 discard/cancellation 路径
traceback/source mapping 保留

Verifier 失败时回退整段原始同步执行。


二十六、我们明确不覆盖什么

一个好的抽象不仅要说能做什么,也要明确不做什么。

1. 不覆盖任意本地 Python constant folding

例如:

x = expensive_pure_python(a)

PLM 不会在 streaming 阶段执行它,也不会自动 partial-evaluate 它。

下面这些属于传统 compiler / partial evaluation 领域:

常量折叠
函数 specialization
loop unrolling
dead local computation elimination
NumPy graph fusion
本地 pure function memoization

它们可与 PLM 组合,但不是同一机制。

2. 不覆盖完整的 Future propagation

PLM 不把:

a + b
a["id"]
obj.method()

变成 Future graph,也不自动生成 Future[Future[T]]。

因此,参数经过任意本地 Python 计算后,后续 Host call 必须等 CPython 真正算出参数。

3. 不覆盖本地 CPU computation 的并行调度

若:

a = A()          # 1s
cpu_work()       # 10s
x = a + 1
b = B(x)         # 20s

A 在 1 秒完成,但主 Python thread 正在做 cpu_work(),B 要到 10 秒后才启动。

要在第 1 秒自动启动 B,需要:

continuation scheduler
第二 Python context
线程/进程并行
合法 code motion
Host expression IR

这超出最小 PLM。

4. 不覆盖任意跨循环迭代并行化

for x in xs:
    y = tool(x)

逐迭代 split 很容易;把所有迭代批量 fan-out 会改变:

异常时机
资源峰值
变量绑定
部分副作用顺序
break/continue
迭代器行为

需要单独 loop transformation 与证明。

5. 不覆盖不可验证的 current read

若 operation 的语义是“真正调用瞬间的当前状态”,且 provider 不提供:

snapshot
version
lease
validation

那么最终值不能安全 pre-read。最多提前连接、认证等时间不敏感阶段。

6. 不覆盖任意不可撤销副作用

真正的:

发送邮件
扣款
删除资源
发布 commit

不能因为 branch speculation 提前发生。除非 provider 支持明确的 prepare/commit 或补偿协议。

7. 不覆盖所有 CPython 反射语义

以下能力会观察 AST 变换或变量绑定时机:

locals()
inspect.currentframe()
sys.settrace()
eval()
exec()

再加上:

Guest threads
signals
real wall-clock
identity-sensitive mutable object
arbitrary native extension

它们必须被禁止、虚拟化或当成 optimization barrier。

8. 不保证找到全局最优 schedule

真实 latency、branch probability、provider contention 和 temporal invalidation 都未知。全局最优是 online stochastic scheduling 问题。

PLM 提供的是:

规范化的合法调度空间
安全的局部变换
保守的 greedy policy

不是一个万能 oracle。


二十七、目前难以做到、但值得研究的优化

1. 受限的 Python continuation

允许少量已证明 pure 的 Python operator 在 Host Future 完成后运行:

A result
  ↓
pure projection / arithmetic
  ↓
B request

这可以缩短 data-dependent chain,但会引入一个小型 continuation runtime。关键研究问题是:如何把范围限制在不重新实现 Python 的程度。

2. Typed projection IR

只支持:

JSON field
tuple index
primitive arithmetic
comparison
formatting

它可以让:

B(a["id"])

在 A 完成后由 Host 自动启动。但 Python 的 __getitem__ 并不总等于 JSON projection,因此必须只对 typed Host result 生效。

3. 跨基本块的 global scheduling

利用完整 PDG、dominance、post-dominance 和 effect summary,把 Prepare 更远地 hoist、Materialize 更远地 sink。收益可能更高,但异常和反射边界会显著增加证明复杂度。

4. Profile-guided temporal scheduling

通过历史 trace 学习:

call site 到达时间
branch reach probability
provider latency distribution
candidate invalidation probability

再选择 just-in-time Prepare 时机。

5. Speculative branch portfolio

在成本预算下选择最值得提前的 branch,而不是全发:

max⁡S∑h∈SPreach(h)Pvalid(h)benefit⁡(h)\max_{S} \sum_{h\in S} P_{\mathrm{reach}}(h) P_{\mathrm{valid}}(h) \operatorname{benefit}(h)

subject to:

∑h∈Scost(h)≤B.\sum_{h\in S} cost(h)\le B.

6. 自动推导 prepare/commit

对某些 side-effecting API,自动把请求分解为:

construct
upload temporary
validate
commit

但这通常需要 provider-specific protocol,不可能只靠 Python AST 推断。

7. 与本地 partial evaluation 组合

若以后加入纯函数证明、typed arrays 或 restricted numerical sublanguage,可以同时:

这会把 PLM 扩展成更完整的 staged optimizer,但应作为独立层,而不是混入最小语义核心。


结语:不是更早执行程序,而是更聪明地安排工作

Pysolate 最容易被误解成:

“模型生成一行,我们就执行一行。”

但更准确、更强的说法是:

我们从不在 source streaming 阶段执行 Python;我们只利用 source 已经暴露的事实,提前生产可能被未来逻辑调用采用的物理工作。

程序的控制流、对象语义、异常和中间状态仍由 sealed 后的一次同步 CPython 执行决定。Host 只拥有:

物理候选
版本证据
pending job
authority contract
effect protocol

三阶段 normal form 将这个思想压缩为:

Prepare at the earliest useful physical time;\boxed{ \textbf{Prepare at the earliest useful physical time;} } Linearize at the original semantic point;\boxed{ \textbf{Linearize at the original semantic point;} } Materialize at the latest observationally safe point.\boxed{ \textbf{Materialize at the latest observationally safe point.} }

或者更短:

物理工作尽早,逻辑生效原位,结果观察尽晚。\boxed{ \text{物理工作尽早,逻辑生效原位,结果观察尽晚。} }

它的通用性来自一个简单事实:现代程序中越来越多的耗时不发生在本地算术里,而发生在受控的系统边界上。只要这个边界能够把“准备”“生效”和“交付”分开,程序就不必为了获得并行性而改变自己的同步心智模型。

这也许是 Pysolate 最重要的设计哲学:

不要提前执行不完整的程序;提前完成完整程序未来可能需要的工作。\boxed{ \text{不要提前执行不完整的程序;提前完成完整程序未来可能需要的工作。} }

参考文献

[1] Barbara Liskov and Liuba Shrira. Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems. PLDI, 1988.

[2] Robert H. Halstead Jr. Multilisp: A Language for Concurrent Symbolic Computation. ACM TOPLAS, 1985.

[3] Yury Selivanov. PEP 492 — Coroutines with async and await syntax;Python asyncio 官方文档。

[4] Honghua Dong et al. APPL: A Prompt Programming Language for Harmonious Integration of Programs and Large Language Model Prompts. ACL, 2025;初版 arXiv:2406.13161.

[5] Jeanne Ferrante, Karl J. Ottenstein, and Joe D. Warren. The Program Dependence Graph and Its Use in Optimization. ACM TOPLAS, 1987.

[6] John Launchbury. A Natural Semantics for Lazy Evaluation. POPL, 1993.

[7] Neil D. Jones, Carsten K. Gomard, and Peter Sestoft. Partial Evaluation and Automatic Program Generation. 1993;Walid Taha and Tim Sheard. Multi-stage Programming with Explicit Annotations. 1997.

[8] Maurice P. Herlihy and Jeannette M. Wing. Linearizability: A Correctness Condition for Concurrent Objects. ACM TOPLAS, 1990.

[9] John M. Lucassen and David K. Gifford. Polymorphic Effect Systems. POPL, 1988.

[10] Cary G. Gray and David R. Cheriton. Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency. SOSP, 1989.

[11] Luca Beurer-Kellner, Marc Fischer, and Martin Vechev. Prompting Is Programming: A Query Language for Large Language Models. PLDI, 2023.

[12] Lianmin Zheng et al. SGLang: Efficient Execution of Structured Language Model Programs. NeurIPS, 2024.

[13] Robert D. Blumofe and Charles E. Leiserson. Scheduling Multithreaded Computations by Work Stealing. JACM, 1999.