Blog Edition 文本附件：保留技术内容，仅适配路径/链接与运行命令。

# grad(f)(3.0) 完整调用时序

基线：HIPS/autograd 1.9.1，commit `f53a21734fdfae636f448744d9097d8d35a643a0`。这是调用关系图，不是源码块；对应 [Lesson 3](https://zdd14990.github.io/blog/applied-math/autograd/03-grad-call-chain/)、[Lesson 4](https://zdd14990.github.io/blog/applied-math/autograd/04-tracer/)、[Lesson 5](https://zdd14990.github.io/blog/applied-math/autograd/05-core-reverse-mode/) 的逐段原文。

## 创建包装器与执行包装器

```text
module import
  unary_to_nary(original unary grad) -> public grad = nary_operator

user: df = grad(f)
  nary_operator(fun=f,argnum=0) -> df = nary_f
  no input value yet; no Box; no node; no g

user: df(3.0)
  nary_f(args=(3.0,),kwargs={})
    creates unary_f: replace args[0] with its supplied x
    calls original unary grad(unary_f,3.0)
      |
      +-> _make_vjp alias -> core.make_vjp
      |     |
      |     +-> VJPNode.new_root -> R(parents=[],vjp(g)=())
      |     +-> trace(R,unary_f,3.0)
      |           |
      |           +-> TraceStack.new_trace -> t=0
      |           +-> new_box(3.0,0,R) -> Bx
      |           +-> unary_f(Bx) -> original f(Bx)
      |           |     |
      |           |     +-> Bx.__mul__(Bx)
      |           |           -> anp.multiply(Bx,Bx)
      |           |           -> primitive.f_wrapped
      |           |                find_top_boxed_args -> positions (0,1), trace0
      |           |                unbox -> argvals=(3.0,3.0)
      |           |                parents=(R,R), argnums=(0,1)
      |           |                recursive f_wrapped -> raw multiply -> 9.0
      |           |                VJPNode.__init__ -> M
      |           |                  primitive_vjps[multiply] -> maker
      |           |                  maker(positions,9,args,kwargs) -> local vjp
      |           |                new_box(9.0,0,M) -> Bz
      |           |     <----- f returns Bz
      |           +-> return (Bz._value,Bz._node) = (9.0,M)
      |                 normal trace exit: top=-1
      |     <----- return (closure capturing M,9.0)
      |
      +-> check vspace(9.0).size == 1
      +-> output ones -> ndarray(shape=(),value=1)
      +-> vjp(1) -> backward_pass(1,M)
            outgrads={M:(1,False)}
            visit M -> pop 1 -> M.vjp(1)=(3,3)
              first edge to R: None + 3 -> (3,False)
              second edge to R: 3 + 3 -> (6,True)
            visit R -> pop 6 -> R.vjp(6)=()
            return 6
  <----- user receives np.float64(6.0), not a Box
```

R/M/Bx/Bz 是教学对象标签；VJPNode 没有 name/value/argnums 字段。`argnums` 与 `parents` 在 raw computation 前收集，节点必须等 answer 算好才创建。图内的顺序对应真实源码，而不是把概念步骤强行排成另一种语句顺序。

## reverse 依赖与梯度流

```text
forward graph:
          arg0
R -----------------\
                    M (local primitive: multiply)
R -----------------/
          arg1
same R object on both edges

backward query:
seed 1 -> M.vjp(1) -> (3,3)
                       | |
                       v v
                    outgrads[R]
                    None -> (3,False) -> (6,True)
                       |
                       v
                 R.vjp(6)=() -> return 6
```

局部 VJP 负责产生贡献，`add_outgrads` 负责汇合，`toposort` 负责让节点在依赖全部完成后才被消费。三种职责不能用一个“反向传播会自动处理”替代解释。
