星期日, 1月 28, 2024

千呼萬喚始出來: AMD XDNA Linux Driver

隨著 AI 的各種應用如雨後春筍般冒出,各種終端裝置也開始強調運算 AI 的能力。比如 Intel 最新發布的 Core Ultra 系列處理器加入了 NPU, AMD 最新發布的 APU 處理器皆加入了 XDNA。

相比 Intel 已經在 Linux 添加了驅動支持,AMD 腳步顯然慢得多。雖然在 github 上早早的公開了其 XDNA 的相關工具 RyzenAI-SW,但卻缺失了 Linux 相關的資源,導致在 issue tracker 上 Linux?中有大量的人在請求相關的資源。

在數天前,AMD 忽然在 github 上公佈了其 Linux Driver AMD XDNA™️ Driver for Linux®️

但目前這個 Driver 尚未整合到 mainline kernel, 也不確定是否有整合計畫,但總歸是個好的開始。

隨著這個 driver 被放出,在 llama.cpp 中的 issue [Feature request] Any plans for AMD XDNA AI Engine support on Ryzen 7x40 processors? 也開始有開發者在等待開發環境的成熟。

只是... AMD 沒有考慮直接將其整合到 ROCM 平台嗎...

星期六, 1月 20, 2024

Linux 下環境變數的儲存

今天心血來潮,忽然好奇環境變數在 Linux 的 Process 中是如何儲存的。因為在 Linux 下可以透過 /proc/$pid/environ 看到程式一開始執行時的環境變數

在 C 語言中,要存取環境變數主要可以透過

  • C functions: getenv, setenv, putenv, ...
  • 全域變數extern char **environ

透過全域變數 extern char **environ,可以知道環境變數的結構如下

在 Linux 下,如果想知道一個記憶體位置是位在哪個區塊,可以從 /proc/self/maps 觀看

在比對 environ, environ array, environ variables 後可以得知

程式開始執行時
  • environ 左值位於執行檔中(因此固定不動)
  • environ array 位於 [stack] 中
  • 所有的 environ varibales 位於 [stack] 中,且所有的 environ variables 緊密排列
當加入一個新的環境變數後或設置環境變數後
  • environ array 位於 [heap] 中
  • 所有的 environ variables 位於 [stack] 中,除了新加入的環境變數會在 [heap] 中

觀察過後會發現利用 C functions 對環境變數的修改都不會直接修改到 [stack] 中的 environ variables。於是猜測說 /proc/$pid/environ 的內容是直接對應到 [stack] 中的那些 environ varibales。於是進行實驗

environ[0][0] = 's';
sh-5.1$ cat /proc/297884/environ | tr '\0' '\n' | head -n 1
sHELL=/bin/bash
sh-5.1$

直接修改 [stack] 中的環境變數內容果然反應到了 /proc/$pid/environ 上面

在 Linux 下面想要改變 /proc/$pid/environ 對應到的 process 記憶體區塊,可以透過 prctl 搭配 PR_SET_MM_ENV_START, PR_SET_MM_ENV_END 來進行修改

以下的程式會使用 prctl 改變環境變數對應的區域,並且輸出環境變數 "SHELL" 與 /proc/$pid/environ 的內容。注意此程式需要使用 root 來跑,否則 prctl 執行會失敗

#include <sys/prctl.h>
#include <stdio.h>
#include <stdlib.h>

int
main()
{
        char buffer[4096];
        size_t nLen = 0;
        FILE *fp = NULL;
        char envs[] = "LINUX=123";

        prctl(PR_SET_MM, PR_SET_MM_ENV_START, &envs[0], 0, 0); 
        prctl(PR_SET_MM, PR_SET_MM_ENV_END, &envs[sizeof(envs)], 0, 0); 

        printf("%s\n", getenv("SHELL"));

        fp = fopen("/proc/self/environ", "r");
        nLen = fread(buffer, 1, sizeof(buffer), fp);
        fclose(fp);

        fwrite(buffer, nLen, 1, stdout);
        fputc('\n', stdout);

        return 0;
}
此程式的輸出為
/bin/bash
LINUX=123

可以看到對程式內部而言,環境變數沒有什麼變化,但從 /proc/$pid/environ 已經變成程式另外指定的區域了

星期一, 1月 01, 2024

Rust 陣列 Option 的初始化

在撰寫一些程式的過程中,需要建立一個 String 的 Array,且陣列元素有可能沒有字串。於是我寫下了以下的 Code。
fn main() {
  let s1: [Option<String>; 26] = [None; 26];
}
在 Rust 中陣列的型別使用 [TYPE; LENGTH],並且可以用 [EXPR; LENGTH] 來進行初始化。 這段程式編譯後會得到以下錯誤。
let s1: [Option<String>; 26] = 
   [None; 26];
    ^^^^ the trait `Copy` is not implemented for `String`
至於原因可以把以上的 Code 展開為
s1[0] = None;
s1[1] = None;
...
s1[25] = None;

因此會要求 [EXPR; LENGTH] 中的 EXPR 需要有 Copy Trait,否則會 Move 導致 s1[1] 以後無法被執行。而 Option<String> 中的 String 無法 Move,因此導致 Option<String> 也無法 Move。

雖然 Option::<String>::None 是不含 String,但 Some, None 都是 enum Option 中的一員,無法通過 Trait Bound 檢查可以理解。但以上的檢查有個意外的繞過方法:使用 const。

fn main() {
	const NONE: Option<String> = None;
	let s1: [Option<String>; 26] = [NONE; 26];
}

const 在 Rust 中表示其值會在編譯期間就決定,而非執行時計算。由於編譯期間就確定 [NONE; 26]中的 None 不可能是 Some,於是檢查就通過了...

在 Rust 中的官方文件有說 [EXPR; LENGTH] 中 Expr 需要符合以下條件之一
  • 有實做 Copy Trait
  • 是 const
如果以後 Rust 編譯器不用 const 關鍵字有發現 None 也是 const 的話,以後就不用寫這麼麻煩了。