前面文章已經(jīng)把openpi論文及代碼解析(A Vision-Language-Action Flow Model for General Robot Control) (一)技術做了梳理, 今天我把之前看的Pi0-Fast, 兩者都是Physical Intelligence公司作主導研發(fā).也進行回顧總結下.
論文地址: FAST: Efficient Action Tokenization for Vision-Language-Action Models
一、 文章閱讀
之前的機器人策略采用按時間步進行分享方案的策略即將連續(xù)的機器人動作映射為離散的動作token, 但是這種動作在執(zhí)行高頻控制的靈巧技能的時候, 方法效果不佳. ?? 不要選擇頻率過高的動作預測, 否則動作token之間的相似性很高, 說簡單點就是復制最近的token一樣簡單, 不利于模型訓練, 容易使得模型陷入局部最優(yōu). 作者采用的策略是在訓練之前需要壓縮機器人動作信號,以減少連續(xù)令牌之間的相關, 這里作者選擇DCT(discrete cosine transform)編碼, 對連續(xù)信號進行轉化.該方法被廣泛應用于壓縮圖像等連續(xù)信號(如JEPG壓縮). ??作者提出了一種基于時間序列壓縮技術的新的FAST token化方案,允許在高頻數(shù)據(jù)上訓練自回歸VLA.
1. 高效分詞策略
機器人動作信號在訓練前需要進行壓縮,以減少連續(xù)token之間的相關性, 他們借鑒了基于壓縮的分詞策略,例如語言模型中常用的字節(jié)對編碼方法「byte-pairencoding,BPE」——通過合并頻繁出現(xiàn)的token序列為新token來壓縮輸入文本. 這里發(fā)明的FAST名字的由來是由頻域動作序列分詞『Frequency-spaceAction Sequence Tokenization,F(xiàn)AST』得名.

且基于FAST,作者還開發(fā)了
FAST+,這是一種通用的機器人動作分詞器,在覆蓋多種機器人結構、動作空間和控制頻率的100 萬條真實機器人動作軌跡上進行訓練他們證明了FAST+ 分詞器能夠有效地對從單臂到雙臂及移動機器人等多種機器人動作序列進行分詞,并且是訓練自回歸VLA 模型的即插即用分詞器. 當與π0 VLA 集成時,基于FAST 的自回歸VLA 能夠擴展到在10k 小時的機器人數(shù)據(jù)上進行訓練,并在多種任務中實現(xiàn)與基于擴散的VLA 相當?shù)男阅?,同時將訓練時間最多縮短至5 倍如下圖所示

當使用簡單的逐時間步標記化方案時,隨著訓練信號控制頻率的增加,該邊際信息趨近于零:對于平滑信號,隨著時間步變短,每個時間步的變化成比例減小。這極大地減緩了訓練期間的收斂速度,并可能使擬合復雜的高頻數(shù)據(jù)集變得困難。高頻動作軌跡中的冗余如何導致每個動作標記的邊際信息較低,從而導致訓練性能不佳。為解決這個問題,我們需要一種將高度冗余的動作信號壓縮為更少高信息標記的標記化方法。作者在這項工作使用基于離散余弦變換(DCT)的壓縮算法。DCT 是一種頻域變換,將連續(xù)信號表示為各種頻率的余弦元素之和。低頻捕獲信號的整體形狀,而高頻分量反映急劇變化。DCT 是壓縮算法(如圖像 JPEG 壓縮)的常用變換,因其簡單性和計算效率,以及對實際圖像的強壓縮特性:由于像素通常平滑變化,DCT 通??梢詢H用幾個系數(shù)表示輸入信號的大部分信息。信號可以通過省略低權重的頻率分量來壓縮。與基于向量量化的學習壓縮方法相比,基于 DCT 的壓縮是一種解析方法,因此極其簡單和快速.
根據(jù)下圖可以看到, 采樣率對預測性能的影響。在教學插值任務上訓練了一個小型的自回歸 transformer 模型,其中網(wǎng)絡必須預測給定四個圓圈的黑色虛線曲線。我們發(fā)現(xiàn),由于高頻下連續(xù)標記之間的強相關性,隨著我們增加底層信號的采樣頻率,使用先前 VLA中使用的分箱標記化方法訓練的模型產(chǎn)生的預測越來越差。我們的 FAST 分詞化方法基于所有采樣率的高質量預測。(下圖左側是以前的自回歸 VLA(如 RT-2、RT-2-X和OpenVLA)使用的每維度分箱方案進行比較——那種方案被稱native tokenization, 下圖右側是FAST所采取的tokenization*), 同時也可以看出在不同采樣頻率下訓練的自回歸模型的平均預測均方誤差(MSE), 但是在DCT壓縮目標token上訓練自回歸模型在各種采樣頻率下始終保持較低的預測誤差. 而如果是使用分箱tokenization的模型,則雖然在低采樣率下實現(xiàn)了良好的預測性能(即較低的MSE),但隨著采樣率的增加,預測誤差急劇增加,最終模型僅僅復制了第一個動作,
高頻下,相鄰token幾乎相同,模型只需抄上一句即可“蒙混過關”。導致只復制第一個 如下圖圖左所示.
學習信號 = “預測下一個 token 時收到的誤差反饋(梯度)”。
邊際信息內(nèi)容 = “在給定歷史上下文的前提下,新 token 出現(xiàn)的“意外”程度
學習信號越大,說明誤差越大,新的token的“意外”程度就越大,促使模型更大幅度地修正以便下次更好地預測。
- 當使用簡單的逐時刻分詞方案時,隨著訓練信號的控制頻率增加,這一邊際信息趨近于零
- 對于平滑信號,隨著時間步變短,每個時間步的變化也按比例減小。這極大地減緩了訓練過程中的收斂速度,并可能導致難以擬合復雜的高頻數(shù)據(jù)集
- 先前的研究中已經(jīng)觀察到了此類挑戰(zhàn)。例如,OpenVLA 在低頻的BridgeV2 和RT-1 數(shù)據(jù)集上表現(xiàn)良好,但在擬合高頻的DROID 數(shù)據(jù)集時遇到了困難

我們接下來看下FAST Tokenization算法

給定一段歸一化后的動作塊,我們首先對其應用離散余弦變換(DCT),將時域信號轉換到頻域。接著對 DCT 系數(shù)量化,并使用字節(jié)對編碼(BPE)將各維度的 DCT 系數(shù)展平成一維序列后壓縮,得到最終的動作 token 序列
首先對輸入動作進行標準化:將訓練集中每個動作維度的第1到第99百分位映射到[?1,1]區(qū)間。這個初始標準化步驟有助于將數(shù)據(jù)帶入指定范圍,并且還使得具有不同動作尺度的跨實體數(shù)據(jù)集的token化更為容易,且使用分位數(shù)來應對大型機器人數(shù)據(jù)集中偶爾出現(xiàn)的異常動作。
在數(shù)據(jù)標準化后,作者對每個動作維度分別應用離散余弦變換DCT,變成頻域系數(shù)對DCT系數(shù)進行壓縮時,可以簡單的省略不重要的系數(shù),通過“縮放+四舍五入”來實現(xiàn),其中縮放比例是一個超參數(shù),控制token化的損失率和壓縮率之間的平衡。
在舍入操作之后,DCT系數(shù)矩陣通常是稀疏的,大多數(shù)條目為零,每個動作維度僅保留少數(shù)幾個重要系數(shù)
為了真正實現(xiàn)壓縮,必須將這個稀疏矩陣轉換為一系列密集的token先將矩陣展平成一維整數(shù)向量,按先低頻后高頻并在維度間交錯的順序拼接,然后訓練字節(jié)對編碼(BPE)分詞器,對其進行無損壓縮,得到緊湊的動作token。
BPE步驟會“壓扁”所有零值分量,并合并跨維度的高頻組合。之所以選用BPE來壓縮DCT矩陣,因為它有眾多高效實現(xiàn),還能生成固定大小的輸出詞表,便于與現(xiàn)有視覺-語言模型詞表無縫集成。其他無損壓縮算法,如哈夫曼編碼或Lempel-Ziv方法(gzip底層算法)也可替代,但留待后續(xù)研究。
注意,在進行BPE編碼前,如何扁平化 |A|×H 的DCT系數(shù)矩陣,對策略訓練效果影響很大。有兩種展平方式:列優(yōu)先——先拼接每個維度的低頻分量;行優(yōu)先——先拼接單個維度的所有頻率分量。列優(yōu)先讓模型先學整體趨勢,行優(yōu)先則先學某個維度全貌,各有得失, 這里選擇列優(yōu)先,因為實驗發(fā)現(xiàn),在自回歸預測時先預測低頻分量(即輸出序列的整體形狀),能帶來更穩(wěn)定的策略執(zhí)行。如此,tokenization流程中的所有操作都易于逆轉,允許快速解碼預測的動作。
該tokenizer只有兩個超參數(shù):用于DCT系數(shù)舍入前的縮放比例,以及BPE步驟的詞匯表大小。作者發(fā)現(xiàn)這兩個參數(shù)都不太敏感,并且在所有的單數(shù)據(jù)集tokenization實驗中使用了相同的值(舍入比例10,BPE詞匯量大小1024)。這與依賴于矢量量化的端到端學習壓縮模塊形成對比(那些模塊往往動輒數(shù)十個超參),需要為每個數(shù)據(jù)集精心調(diào)參才能獲得良好的重構效果。

通用機器人動作tokenizer: FAST+
FAST分詞器的唯一學習組件是 BPE 編碼器的詞匯表,這需要為每個新數(shù)據(jù)集進行訓練,以便應用該分詞器,雖然該訓練過程很快(通常僅需幾分鐘),但仍為使用FAST引入了額外的門檻。
因此,作者的目標是再訓練一個通用動作分詞器,能對來自任意機器人的動作塊進行編碼。為此,作者使用上述流程在一個大型跨形態(tài)機器人動作數(shù)據(jù)集上訓練分詞器,該數(shù)據(jù)集由大約一百萬個1秒的動作chunk組成,涵蓋單臂、雙手和移動操作機器人,具有關節(jié)和末端執(zhí)行器控制動作空間以及各種控制頻率——他們在附錄A中詳細列出了用于訓練通用分詞器的數(shù)據(jù)混合集。對于許多數(shù)據(jù)集,他們包括了多個動作空間參數(shù)化版本:聯(lián)合空間、末端執(zhí)行器世界框架和末端執(zhí)行器攝像頭框架,以確保生成的分詞器的通用性。Open X-Embodiment 、DROID 和 Bridge V2 以其原始形式包含在內(nèi)。

一旦訓練完成,通用動作分詞器FAST+即可以作為一個黑箱分詞器,應用于任何機器人設置的1秒動作序列。且實驗評估顯示,它與為單個數(shù)據(jù)集調(diào)優(yōu)的分詞器具有競爭力。作者將預訓練好的通用動作分詞器 FAST+ 封裝為 HuggingFace 的 AutoProcessor 類,用戶只需三行代碼即可在任意新動作塊上調(diào)用。
from transformers import AutoProcessor
tokenizer = AutoProcessor.from_pretrained(
"physical-intelligence/fast",
trust_remote_code=True
)
tokens = tokenizer(action_chunk)
# 為獲得最佳壓縮效果,建議如4.2所述,先將輸入動作通過分位數(shù)標準化到[?1,1]后,再以1秒為單位進行token化。這個模塊同樣支持在給定動作塊數(shù)據(jù)集上快速訓練新的FAST分詞器:
from transformers import AutoProcessor
tokenizer = AutoProcessor.from_pretrained(
"physical-intelligence/fast",
trust_remote_code=True
)
new_tokenizer = tokenizer.fit(action_dataset)







打個比方
自回歸:像寫作文,一字一句接著寫,寫到哪兒就決定哪兒。
擴散式:像整體給你一塊泥,用雕塑刀逐步修整,最后一次性出爐一尊精致雕像。
擴散式 VLA 就是用去噪的方式并行地“雕刻”全序列動作,再量化成離散 token,而不是像大模型那樣一句一句地自左向右生成。
這里面給到的FastTokenizer代碼如下所示:
class FASTTokenizer:
def __init__(self, max_len: int = 256, fast_tokenizer_path: str = "physical-intelligence/fast"):
self._max_len = max_len
# Download base PaliGemma tokenizer
path = download.maybe_download("gs://big_vision/paligemma_tokenizer.model", gs={"token": "anon"})
with path.open("rb") as f:
self._paligemma_tokenizer = sentencepiece.SentencePieceProcessor(model_proto=f.read())
# Instantiate FAST tokenizer
self._fast_tokenizer = AutoProcessor.from_pretrained(fast_tokenizer_path, trust_remote_code=True)
self._fast_skip_tokens = 128 # Skip last 128 tokens in PaliGemma vocab since they are special tokens
def tokenize(
self, prompt: str, state: np.ndarray, actions: np.ndarray | None
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
cleaned_text = prompt.lower().strip().replace("_", " ")
# Convention: state gets discretized into 256 discrete bins (assumed range after normalization: [-1, 1])
discretized_state = np.digitize(state, bins=np.linspace(-1, 1, 256 + 1)[:-1]) - 1
# Convention: prefix includes prompt and string-representation of state, followed by ';'
state_str = " ".join(map(str, discretized_state))
prefix = f"Task: {cleaned_text}, State: {state_str};\n"
prefix_tokens = self._paligemma_tokenizer.encode(prefix, add_bos=True)
if actions is not None:
# Tokenize actions with FAST tokenizer --> map to last tokens in PaliGemma vocab
action_tokens = self._fast_tokenizer(actions[None])[0]
action_tokens_in_pg = self._act_tokens_to_paligemma_tokens(action_tokens)
# Convention: postfix contains 'Action:' followed by FAST tokens, followed by '|'
postfix_tokens = (
self._paligemma_tokenizer.encode("Action: ")
+ action_tokens_in_pg.tolist()
+ self._paligemma_tokenizer.encode("|", add_eos=True)
)
else:
postfix_tokens = []
# Create output token sequence & masks
# AR mask is 0 on prefix (bidirectional attention) and 1 on postfix (causal attention to all previous tokens)
tokens = prefix_tokens + postfix_tokens
token_mask = [True] * len(tokens)
ar_mask = [0] * len(prefix_tokens) + [1] * len(postfix_tokens)
loss_mask = [False] * len(prefix_tokens) + [True] * len(postfix_tokens) # Loss on postfix only
# Pad tokens to max length
tokens_len = len(tokens)
if tokens_len < self._max_len:
padding = [False] * (self._max_len - tokens_len)
tokens = tokens + padding
token_mask = token_mask + padding
ar_mask = ar_mask + padding
loss_mask = loss_mask + padding
else:
if len(tokens) > self._max_len:
logging.warning(
f"Token length ({len(tokens)}) exceeds max length ({self._max_len}), truncating. "
"Consider increasing the `max_token_len` in your model config if this happens frequently."
)
tokens = tokens[: self._max_len]
token_mask = token_mask[: self._max_len]
ar_mask = ar_mask[: self._max_len]
loss_mask = loss_mask[: self._max_len]
return np.asarray(tokens), np.asarray(token_mask), np.asarray(ar_mask), np.asarray(loss_mask)
def extract_actions(self, tokens: np.ndarray, action_horizon: int, action_dim: int) -> np.ndarray:
# Decode predicted output tokens
decoded_tokens = self._paligemma_tokenizer.decode(tokens.tolist())
# Extract actions from FAST model outputs
if "Action: " not in decoded_tokens:
return np.zeros((action_horizon, action_dim), dtype=np.float32)
# Extract actions from decoded tokens
raw_action_tokens = np.array(
self._paligemma_tokenizer.encode(decoded_tokens.split("Action: ")[1].split("|")[0].strip())
)
action_tokens = self._act_tokens_to_paligemma_tokens(raw_action_tokens)
return self._fast_tokenizer.decode(
[action_tokens.tolist()], time_horizon=action_horizon, action_dim=action_dim
)[0]
def _act_tokens_to_paligemma_tokens(self, tokens: np.ndarray | list[int]) -> np.ndarray:
if isinstance(tokens, list):
tokens = np.array(tokens)
return self._paligemma_tokenizer.vocab_size() - 1 - self._fast_skip_tokens - tokens
參考:
[1] 自回歸版π0-FAST——打造高效Tokenizer:比擴散π0的訓練速度快5倍但效果相當(含π0-FAST源碼剖析)
[2] π0-FAST-針對VLA模型的高效動作token化技術-2025.1.16-開源