Mar 22, 2025
6 min read
Rust,
Candle,
Pytorch,

Rust Candle 框架与 Pytorch 张量等价操作之逐点运算

本文对比了Pytorch和Rust Candle框架在张量逐点运算上的实现差异,涵盖绝对值、三角函数、指数等常见操作。

逐点运算,即对张量的每个元素独立操作。Pytorch 有大量的运算 api 支持逐点运算,这里尽量讲一些常见的运算在 candle 中的等价操作。

逐点运算表格预览: ✅ 表示有对应实现 🚫 表示无对应实现 ☢️ 表示有代替实现

操作PytorchCandle
绝对值abs、absoluteabs
反余弦值/反正弦值/反正切值acos(arccos)/asin/atan未实现🚫
余弦值/正弦值/正切值cos/sin/tancos/sin/ (tan 未实现)🚫
反双曲余弦值acosh、arccosh未实现🚫
加/减/乘/除add/sub/mul/divadd/sub/mul/div
向上取整/向下取整ceil/floorceil/floor
所有元素夹紧到范围 [ min, max ] 内clamp、clipclamp
双曲余弦值cosh未实现🚫
角度(度)转换为弧度deg2rad未实现🚫
e的指数expexp
截断整数值fix、trunc未实现🚫
浮点数张量的幂float_powerpowf
截断取小数部分frac未实现🚫
分解尾数和指数张量frexp未实现🚫
自然(e)对数loglog
其他对数log10/log2/log1p/log**全部未实现🚫
负数neg、negativeneg
倒数reciprocalrecip
平方根/平方根倒数sqrt/rsqrtsqrt/未实现,有代替方案☢️
逻辑 sigmoid 函数sigmoid、torch.special.expitcandle_nn::ops::sigmoid
取符号值signsign
softmaxsoftmaxcandle_nn::ops::softmax
平方square未实现,有代替方案☢️

绝对值

pytorch

	a = torch.tensor([-1,2,-3])
    # 输出 tensor([1, 2, 3])
    print(a.abs())

candle:

    let a_data = vec![-1i64, 2, -3];
    let a = Tensor::from_vec(a_data, 3,&Device::Cpu)?;
    let y = a.abs()?;
    //[-1, 2, -3] -> [1, 2, 3]
    println!("{y}");

余弦值/正弦值/正切值

candle 中,只有 cos 和 sin 实现了,tan 未实现。cos 和 sin 默认只支持浮点型。

pytorch

    a = torch.tensor([-1,2,-3])
    print(a.cos()) #tensor([ 0.5403, -0.4161, -0.9900])
    print(a.sin()) #tensor([-0.8415,  0.9093, -0.1411])
    print(a.tan()) #tensor([-1.5574, -2.1850,  0.1425])
 

candle

    let a_data = vec![-1., 2., -3.];
    let x = Tensor::from_vec(a_data, 3,&Device::Cpu)?;
    let a = x.cos()?; 
    let b = x.sin()?; 
    println!("{a}");//[ 0.5403, -0.4161, -0.9900]
    println!("{b}");//[-0.8415,  0.9093, -0.1411]

虽然 candle 不支持 tan 正切操作,但是通过数学公式我们可知道:

tan(x)=cos(x)/sin(x)​

因此 tan 有间接实现:

    // 正切操作
    let c = (x.sin()? / x.cos()?)?;
    println!("{c}"); //[-1.5574, -2.1850,  0.1425]

加/减/乘/除

pytorch 里的加/减/乘/除方法与 candle 加/减/乘/除方法常规用法大体一致。但是,还有一些细节 Canele 未实现:

  1. pytorch 的 加法和减法有 alpha 缩放,而 candle 没有,也就是当我们不使用 alpha 缩放参数时,其操作才和 candle 中的操作等价。
  2. pytorch 里的加/减/乘/除方法不但可以支持张量与张量之间,还支持和标量数字之间运算。

pytorch

    a = torch.tensor([1,2,3])
    print(a.add(10)) #tensor([11, 12, 13])
    print(a + 10) # tensor([11, 12, 13])

candle

    let a_data = vec![1., 2., 3.];
    let x1 = Tensor::from_vec(a_data, 3,&Device::Cpu)?;
    // candle 不支持不同尺寸的 Tensor 做加/减/乘/除操作
    // 也不支持标量 和 Tensor 做加/减/乘/除操作,因此需要把标量 转成 Tensor
    let size = 3;
    let b_data = [10f64].repeat(size);
    let x2: Tensor = Tensor::from_vec(b_data, size,&Device::Cpu)?;
    let y: Tensor = x1.add(&x2)?;
    println!("{y}"); //[11., 12., 13.]
    let y = (x1 + x2)?;
    println!("{y}"); //[11., 12., 13.]

向上取整/向下取整

pytorch

    a = torch.tensor([ 0.5403, -0.4161, -0.9900])
    print(a.ceil())  #tensor([1., -0., -0.])
    print(a.floor()) #tensor([ 0., -1., -1.])

candle

    let a_data = vec![ 0.5403, -0.4161, -0.9900];
    let x = Tensor::new(a_data, &Device::Cpu)?;
    let ceil = x.ceil()?;
    let floor = x.floor()?;

    println!("ceil: {:?}", ceil); //ceil: Tensor[1, -0, -0; f64]
    println!("floor: {:?}", floor);//floor: Tensor[0, -1, -1; f64]
    

元素夹紧到范围 [ min, max ] 内

公式如下:

yi=min(max(xi,min_valuei),max_valuei) y_i​=min(max(x_i​,min\_value_i​),max\_value_i​)

pytorch

    a = torch.tensor([ 0.5403, -0.4161, -0.9900])
    print(a.clamp(-0.5,0.5)) #tensor([ 0.5000, -0.4161, -0.5000])

candle

    //x = [ 0.5403, -0.4161, -0.9900]
    let y = x.clamp(-0.5, 0.5)?;
    println!("{y}"); //[ 0.5000, -0.4161, -0.5000]

e 的指数

公式如下:

yi=exiy_i​=e^{x_i}​

pytorch

    a = torch.tensor([ 0., -2., -3.])
    print(a.exp()) #tensor([1.0000, 0.1353, 0.0498])

candle

    //x = [ 0., -2., -3.]
    let y = x.exp()?;
    println!("{y}"); //[1.0000, 0.1353, 0.0498]

截断整数值

这个操作在 candle 中未实现,可能需要根据具体的 tensor 转成 向量,转换后再返回 tensor,不过这个实现的代价有点大。

    let a_data = vec![ 1.0000, -2.9353, -5.0498];
    let x = Tensor::new(a_data, &Device::Cpu)?;
    let v = x.to_vec1::<f64>()?;
    let v:Vec<f64> = v.iter().map(|v| v.trunc()).collect();
    let y = Tensor::new(v, &Device::Cpu)?;
    println!("{y}");//[ 1., -2., -5.]

浮点数张量的幂

pytorch

    a = torch.tensor([ 6.0, 4.0, 7.0, 1.0])
    print(a.float_power(2)) #tensor([36., 16., 49.,  1.], dtype=torch.float64)

candle

    let a_data = vec![6., 4., 7., 1.];
    let x = Tensor::new(a_data, &Device::Cpu)?;
    let y = x.powf(2f64)?;
    println!("{y}");//[36., 16., 49.,  1.]

截断取小数部分

和截断整数值 操作一下,candle 中未实现,可以转回向量操作:

    let a_data = vec![ 1.0000, -2.9353, -5.0498];
    let x = Tensor::new(a_data, &Device::Cpu)?;
    let v = x.to_vec1::<f64>()?;
    let v:Vec<f64> = v.iter().map(|v| v.fract()).collect();
    let y = Tensor::new(v, &Device::Cpu)?;
    println!("{y}");//[ 0.0000, -0.9353, -0.0498]

自然对数

公式:

yi=loge(xi)y_i​=log_e​(x_i​)

pytorch

    a = torch.tensor([ 6.0, 4.0, 7.0, 1.0])
    print(a.log()) #tensor([1.7918, 1.3863, 1.9459, 0.0000])

candle

    let a_data = vec![ 6.0, 4.0, 7.0, 1.0];
    let x = Tensor::new(a_data, &Device::Cpu)?;
    let y = x.log()?;
    println!("{y}");//[1.7918, 1.3863, 1.9459, 0.0000]

负数

pytorch

    a = torch.tensor([ 6.0, 4.0, 7.0, 1.0])
    print(a.neg()) #tensor([-6., -4., -7., -1.])

candle

    let a_data = vec![ 6.0, 4.0, 7.0, 1.0];
    let x = Tensor::new(a_data, &Device::Cpu)?;
    let y = x.neg()?;
    println!("{y}");//[-6., -4., -7., -1.]

倒数

公式如下:

outi=1inputiout_i​=\frac{1}{input_i}​

pytorch

    a = torch.tensor([1.0, 2.0,3.0])
    print(a.reciprocal()) #tensor([1.0000, 0.5000, 0.3333])

candle

    let a_data = vec![1.0, 2.0,3.0];
    let x = Tensor::new(a_data, &Device::Cpu)?;
    let y = x.recip()?;
    println!("{y}");//[1.0000, 0.5000, 0.3333]

平方根/平方根倒数

平方根公式

outi=inputi​​out_i​=\sqrt{input_i}​​

平方根倒数公式

outi=1inputiout_i​=\frac{1}{\sqrt{input_i}}

pytorch

    a = torch.tensor([1.0, 2.0,3.0])
    print(a.sqrt()) #tensor([1.0000, 1.4142, 1.7321])
    print(a.rsqrt()) #tensor([1.0000, 0.7071, 0.5774])

candle 没有 rsqrt, 但是可以通过上面的公式算出来。

    //[1.0, 2.0,3.0]
    let y = x.sqrt()?;
    println!("{y}");//[1.0000, 1.4142, 1.7321]
    let y = (1f64 / x.sqrt()?)?;
    println!("{y}");//[1.0000, 0.7071, 0.5774]

sigmoid

sigmoid 函数常被用作激活函数,用于将神经元的输出压缩到 (0,1) 之间,从而可以表示概率值。

公式如下:

outi=11+einputiout_i​=\frac{1}{1+e^{−input_i}}​

这个操作 pytorch 和 candle 有一些不一样,而且默认 pytorch 的精度显示也和 candle 不一样。

pytorch

    a = torch.tensor([1.0, 2.0,3.0])
    print(a.sigmoid()) #tensor([0.7311, 0.8808, 0.9526])

candle

    //[1.0, 2.0,3.0]
    let y = candle_nn::ops::sigmoid(&x)?;
    println!("{:?}",y);//Tensor[0.7310585786300049, 0.8807970779778823, 0.9525741268224334; f64]

取符号值

取符号值的意思是,当x < 0 时,返回 -1,当 x = 0 时,返回 0,当 x > 0 时,返回 1。 数学公式如下:

sign(x)={1,x<00,x=01,x>0sign(x)= \begin{cases} -1,\,\,x<0\\ 0,\,\,x=0\\ 1,\,\,x>0\\ \end{cases}​

pytorch

    a = torch.tensor([1.0, -2.0,3.0])
    print(a.sign()) #tensor([ 1., -1.,  1.])

candle

    //[1.0, -2.0,3.0]
    let y = x.sign()?;
    println!("{:?}",y);//Tensor[1, -1, 1; f64]

Softmax

softmax 是一种数学函数,它将一个数值向量转换为一个概率分布向量。softmax 计算后得到一个新的向量,其中每个元素表示对应输入在整个向量中的概率占比。

softmax 常用于分类任务中。

公式如下:

Softmax(xi)=exp(xi)jexp(xi)Softmax(x_i​)=\frac{exp(x_i)}{\sum_j exp(x_i​)}​

pytorch

    a = torch.tensor([1.0, -2.0,3.0])
    print(a.softmax(0)) #tensor([0.1185, 0.0059, 0.8756])

candle

    //[1.0, -2.0,3.0]
    let y = candle_nn::ops::softmax(&x, 0)?;
    println!("{:?}",y);//Tensor[0.11849965453500959, 0.005899750401902781, 0.8756005950630876; f64]

平方

pytorch 中平方有两种写法:

    a = torch.tensor([1.0, -2.0,3.0])
    print(a.square()) #tensor([1., 4., 9.])
    print(a.pow(2)) #tensor([1., 4., 9.])

candle 是没有 square 的,只能用 powf 去实现。

    //[1.0, -2.0,3.0]
    let y = x.powf(2.)?;
    println!("{:?}",y);//Tensor[1, 4, 9; f64]