2025 年 Q2 投資回顧

先講結果, Q2 整體投資淨值比 2024 年底低了 0.78%, 但已經比 Q1 好上 0.76%, 差不多就是這兩個時間點的平均值. 換言之, 算是連滾帶爬地救回了一半.

雖然美股那邊已經回到高點, 甚至創新高. 但是拜台幣半年升值 10.69% 之賜, 我的投資組合漲得不如匯率跌得兇, 故還在虧損階段.

2024/12/31~2025/6/30 的投資淨值, 就以貝里幣示意如下.

相較於 2025 年 Q1 的規劃是去除雜質, 我在 Q2 沒有什麼大動作. 主要就是用定期定額買 QQQ 和 PFF. 作為雜質代表的 KO, 成了我想賣又捨不得賣的懸念.

要賣掉 KO 當然是選個股價高點. 但很可惜地現在卻是匯率低點. 我的 KO 是用台幣複委託買的, 不要說有沒有創新高了, 現在賣掉就現虧 10% 匯損. 我看我同事 (Ri)2 這幾週開會都不喝可樂了,與其等待 KO 股價再漲 10%, 還不如期待美元指數周期性的輪動來得實在.

因為 QQQ 買了又買, 現在它已經佔我的投資組合一半以上了.

這半年來確實有考慮買一些歐洲的 ETF, 像是 VGK. 因為台幣兌歐元也明顯升值, 歐股相對是便宜價. 不過一來現在閒錢不太夠用, 不知道要賣誰來換比較好? 二來是考慮到穩定收益對我更重要, 所以沒有實際做出投資.

說到穩定收益, 目前主要靠 PFF 和 SHEL 的配息. PFF 的股價有點下跌, 從近期高點下滑 8.66%. 不過這個表現還是比美債 20 年 ETF 那些好一倍 (抓元大美債 20 年 ETF 的話, 大約是掉了 17.3%). SHEL 股價距離最高點掉了 5.44%, 我認為這屬於正常波動範圍, 可以接受.

同樣都是石油股, 巴西石油 (PBR) 的各項指標似乎都比殼牌好 [1]. 不過再怎麼說, 我對殼牌的信心還是高一點. 我不太能想像以後要靠巴西人賺退休金的情境.

講到退休, 常常聽到只要存一筆錢, 每年提領 4% 就好這種論調. 或者反推現在每年要用多少錢, 就要準備 25 倍於那個數字. 然而, 其實賺多少和花多少不一定會連動, 還是跟個人生活型態有關. 如果本來花錢就大手大腳, 退休後只能綁手綁腳, 那餘生的心情一定都很糟. 除非自己是個自我 PUA 的大師~~~

像我們家那麼窮, 問題就不大. 只要退休後的所得替代率還過得去, 我在退休前後都可以保持我的生活樣貌. 我心目中的所得替代率共有兩個, 分別對應月薪和年薪. 以下都是考慮稅後的數字.

(1) 月薪版: 股債配息 + 勞退/勞保年金 vs 月薪 + 公提金

(2) 年薪版: 每年總資產淨值成長幅度 vs 年薪

原本在 Q1 的時候, 我就很開心地達到了月薪替代率 90%. 但人算不如天算, 美金貶值後月薪替代率就只剩下 81.83 %. 這表示如果退休了, 麥當勞只能點小薯、中可, 所幸還是能吃到大麥克.

至於年薪方面, 過去 12 年累積投資獲利是年薪總和的 1.94 倍. 雖然開頭幾個年頭比較多次低於 100%, 但是從來沒有變成負數過. 基本上, 股息再投入的全市場投資是複利計算, 即使獲利率不變, 期望值也會愈來愈大. 只有變異數無法預知, 畢竟高低起伏在所難免.

而每年的年薪都是獨立事件, 有高有低, 更不可能連續暴漲. 照理說, 長期下來薪水這方就會逐漸被投資拋開才對. However, 這是兩個隨機變數之間的比較, 目前投資這邊雖然輸了. 但樂觀來說, 今年還剩下一半. 讓我們拭目以待整年度的成果如何吧?

即使 2025 年真的成為台幣計價的美股荒年, 今年過了還有明年. 明年過了還有後年,… 長期來說, 持續投資全市場應該輸不了! 雖然我不是經濟學家, 也不是預言家. 我相信事情應該是如此發展. 萬一不是這樣呢, 也頂多是以後改吃小薯小可加上吉士堡而已, 問題不大. 更何況, 萬一2025/7/5 就是世界末日 [2], 想那麼多也沒用不是嗎? 哈!

[REF]

  1. PBR与SHEL:股票综合比较
  2. https://esg.gvm.com.tw/article/85276

資產小筆記

資產可以分為金融資產 (financial assets) 和實物資產 (real assets) 兩類. 實物資產的價值就是它的現值, 金融資產考慮的是它未來的價值折現後的現值.

Peter Bernstein 說 “Financial Markets are a kind of time machine that allows selling investors to compress the future into the present and buying investors to stretch the present into the future."

[按照請求權是否固定分類]

金融資產又分為 fixed amount claim (固定金額請求權) 和 residual or equity claim (剩餘或股權請求權). “claim"指的是投資者對金融資產的請求權或所有權.

“固定金額的 claim" 代表投資者擁有固定的本金和收益, 例如買了債券, 就會收到固定的利息.

“剩餘或股權的 claim" 表示投資者的收益取決於公司的經營狀況, 例如買了股票, 公司賺得多就分得多. 優先股有債券性質, 但還是跟股票分在一類.

[按照到期日長短分類]

金融市場又分為貨幣市場 (money market) 和資本市場 (capital market).

一年以內到期的債券屬於 money market, 超過一年期的債券和股票 (含優先股) 屬於 capital market.

所以, 巴菲特買一年期國債, 是從資本市場撤到貨幣市場. 對金融市場來說, 竟然超過一年就算是長期投資. 真是有點驚訝!

[REF]

  1. https://xueqiu.com/1383267494/132605480

PPO 小註解

Proximal Policy Optimization 是一個 fine-tune Model 的方式, 另一個主流方式是 DPO (Direct Preference Optimization). 這邊主要是整理課程影片中 PPO 的公式 (只用大約 2 分鐘超高速閃過…), 以便有空時可以查詢.

[基本架構]

有一個 LLM model, 和可調整的參數: θ, query: X, response: Y, 所有輸出入組合 rollout 表示為 (X, Y).

因為我們要微調這個 model, 所以要給一個獎勵函數 (reward function): r(X,Y), 用來調整模型 πθ (X,Y).

獎勵是對所有輸出的 Y 取期望值

EY~πθ (X,Y) [r(X,Y)], 其中 Y~πθ (X,Y), 表示 Y 在 πθ (X,Y) 的輸出組合.

也對每個 query X 計算

EX~D[EY~πθ (X,Y) [r(X,Y)]] ——————— (1)

其中 X~D, D 表示 X 的散度 (Divergence), 依此類推.

我們找出一個得到最多獎勵的那組參數, 就算是 fine-tune 完成.

π* (X,Y) = arg maxπ {EX~D[EY~πθ (X,Y) [r(X,Y)]]}, {} 裡面就是上面那包.

[Reference model]

做得好不好, 我們要有一個 reference model 來當評審.

πref (X,Y)

Model 的輸出愈接近 reference model 就表示學得愈好.

[懲罰值]

KL 是 Kullback-Leibler 兩個人名的縮寫. KL penality coefficient 是它們衡量懲罰的函數. 基於 reference model πref (X,Y) 下, 對 πθ (X,Y) 產生的懲罰 DKL 表示為

DKLθ (X,Y) || πref (X,Y)]

[綜合期望值和懲罰值]

π* (X,Y) = arg maxπ {EX~D[EY~πθ (X,Y) [r(X,Y)]] – β * 懲罰值} =>

π* (X,Y) = arg maxπ {EX~D[EY~πθ (X,Y) [r(X,Y)]] – β DKLθ (X,Y) || πref (X,Y)]}


為了要知道往哪個方向調整, 我們要找 θ 的梯度.

[只看 reward function]

這是 reward function 在特定 model 參數 θ 的期望值

E[rY ] = EY~πθ (X,Y) [r(X,Y)]

將它表示為解析函數 [1], 期望值等於所有 Y 可能的值乘上它的機率的和

E[rY ] = ∑Y r(X,Y)πθ (Y|X) —— (2)

最優化的參數 θ⌃ 就是所有 θ 中 reward 最好的. (我打不出 hat theta, 所以把帽子放右邊)

θ⌃ = arg maxθ [E[rY ]] = arg maxθ [∑Y r(X,Y)πθ (Y|X)]

對式子 (2) 取 θ 的梯度,

θ E[rY ] = ∑Y r(X,Y)θ πθ (Y|X)] ——– (3)

按照微積分的 chain rule [3]

θ log (πθ (Y|X)) = θπθ (Y|X) / πθ (Y|X) =>

θπθ (Y|X) = θ log (πθ (Y|X)) * πθ (Y|X) —– (4)

把這個變長的 (4) 帶回去 (3),

θ E[rY ] = ∑Y r(X,Y)θ πθ (Y|X)] =>

θ E[rY ] = ∑Y r(X,Y)θ log (πθ (Y|X)) * πθ (Y|X) =>

θ E[rY ] = EY~πθ (Y|X) r(X,Y)θ log (πθ (Y|X))

再把 X 考慮進去, 就得到

Ex~D [θ E[rY ] ] = θ Ex~D [ E[rY ] ]

意思是說, 當 β = 0, 忽略掉懲罰的話, 對式子 (1) 取梯度, 等效只對 reward function 取梯度. 反之亦然.

這有什麼意義呢? 我請 AI 幫我解釋. 我試了幾次都沒有它講得好.

θ E[rY ] = EY~πθ (Y|X) r(X,Y)θ log (πθ (Y|X)) 這個結果是 策略梯度定理(Policy Gradient Theorem)[2] 的核心公式。其意義在於:

  • 將梯度轉換為期望形式:原始梯度需遍歷所有可能的輸出 θY(計算量巨大),但此公式表明:梯度可通過從策略 πθ   中採樣 Y 來近似計算。
  • 避開解析計算:無需知道所有 Y 的機率分佈,只需對採樣的 Y 計算  r(X,Y)θ log (πθ (Y|X)) 的平均值。

它與 Monte Carlo Method 的相似處在於: 透過採樣直接估計期望值,無需精確計算所有可能的 Y.

蒙特卡羅方法收斂速度為 O(1/√N​), N 夠大就好. 而窮舉法不可行.

最後再筆記一下懲罰項.

Kullback-Leibler 方法其實是要避免訓練後新的參數和舊的參數偏差太遠. 以至於學會新的東西就忘了舊的 – 災難性遺忘 (Catastrophic Forgetting), 和 reward 無關. 因此討論原理時可以忽略它.

但它對於穩定系統非常重要, 其主要角色是作為正則化項 (Regularizer), 將策略更新限制在信賴域 (Trust Region) 內. 在PPO算法中,KL 散度還會動態調整更新步長. 當 DKL​ 超過閾值時自動縮小學習率,此設計本質上將 “避免遺忘" 和 “促進學習" 綁定為同一過程。

[REF]

  1. 解析函數 – 維基百科,自由的百科全書
  2. https://en.wikipedia.org/wiki/Policy_gradient_method
  3. Slide 1

GGUF 小註解

拜台幣升值 10% 左右之賜, 我在 Q2 的投資差點要 GG. 我期望不高, 只要月底能跟 Q1 對齊, 也算是有 10% 看不見的成長了. 在這混亂的國際局勢下, 我來筆記一下 GGUF. 它不是 “GG, U Failed" 的縮寫! 而是全名 GPT-Generated Unified Format, 一個為了 LLM 推理而設計出的檔案格式.

一般我們去 Huggingface 下載一個模型, 它會可能是 .bin 檔, safetensor 檔, 或是 ONNX 檔. 以 safetensor 而言, 裡面放的是模型未量化過的模型權重. 至於 model 的架構 (graph 長相), 會另外寫在 config.json 裡面. 雖然這個檔案名稱也通用於很多其他的地方, 這裡特別是指用來描述模型的 config.json.

例如 Gemma-2-2B 的 config.json [1] :

{
  "architectures": [
    "Gemma2ForCausalLM"
  ],
  "attention_bias": false,
  "attention_dropout": 0.0,
  "attn_logit_softcapping": 50.0,
  "bos_token_id": 2,
  "cache_implementation": "hybrid",
  "eos_token_id": 1,
  "final_logit_softcapping": 30.0,
  "head_dim": 256,
  "hidden_act": "gelu_pytorch_tanh",
  "hidden_activation": "gelu_pytorch_tanh",
  "hidden_size": 2304,
  "initializer_range": 0.02,
  "intermediate_size": 9216,
  "max_position_embeddings": 8192,
  "model_type": "gemma2",
  "num_attention_heads": 8,
  "num_hidden_layers": 26,
  "num_key_value_heads": 4,
  "pad_token_id": 0,
  "query_pre_attn_scalar": 256,
  "rms_norm_eps": 1e-06,
  "rope_theta": 10000.0,
  "sliding_window": 4096,
  "torch_dtype": "float32",
  "transformers_version": "4.42.4",
  "use_cache": true,
  "vocab_size": 256000
}

使用 GGUF 就不用單獨的 config.json 了, 但是生成 GGUF 的時候還是需要. 所以從Huggingface 下載 GGUF 時看到 config.json 也不用太驚訝. 總之, GGUF 檔案裡面除了放模型的權重, 還放了超參數和詞彙表. 所謂超參數就是模型訓練中需要手動設定、無法透過模型訓練的參數 – 這些就是從 config.json 抄過來的.

至於詞彙表 (Vacabulary) 裡面就是放所有 toekn 的字典. 它有一點大, 但不會太大. 假設u有個模型認得 128K 個 token (大約是 GPT-4 的用量), 每個 token 長度 4 bytes, 算起來才 5.1MB, 比起模型權重動輒就是幾 GB, 真的也不差多哪一點.

GGUF 把這些都打包起來, 使用時就不用東市買駿馬,西市買鞍韉,南市買轡頭,北市買長鞭. 假設要量化參數, 下個指令轉出新的 .gguf 檔就好. 包括混合精度也能做到. 更不會改了參數檔, 結果 config.json 沒改到. 我們常常會遇到這個 bug.

雖然 GGUF 把相關資料都包在一起, 但它執行起來並不像個肥宅, 甚至它更省記憶體. 如果我們使用 safetensor, 因為那一整包都是權重, 我們無法知道哪一個 byte 是屬於哪一層的參數, 所以通常整包都得放進記憶體. 像是 Llama 3 70B 量化過也需要用 30GB 記憶體.

反觀 GGUF 自己就有完整的訊息, 它可以把檔案中的模型 memory mapping 到 virtual memory 裡面, run 到哪個 chunk, CPU 或 GPU 直接看 index 載入哪部分的參數即可, 號稱可以作到 zero copy. 以前述 Llama 3 70B 模型為例, 可能只需要 500MB~2GB 的記憶體就夠了. 據說在一台 16GB 的筆電上也能運行.

另外, safetensor 檔案裡面只放權重, 還大剌剌地以 safe 為名. GGUF 包了更多東西進去, 那還能保證安全嗎? 答案也是肯定的. 它對格式和 API 都有所規範, 並內建 SHA-256 校驗, 所以不至於輕易地被駭客埋 code 進去, 並且被當作 Python 執行. 至於 pickle (.pt) 或是 .bin 檔案則是有這方面風險.

最後談 GGUF 的缺點. 首先是它只支援 LLM, 不像 ONNX 適用於所有深度學習 (Yolo 等等)、safetensor 更適用於儲存所有 tensor. 其次是背後沒有大人認養, 所以 toolchain 比較不足. 最後的主要缺點是 – 它對 CPU 優化較好, 同一個 model 用 ONNX 表示的話, 在 GPU 上會跑得更快.

最後叫 Monica 生成一個簡單的 GGUF 檔案範本, 這樣比較好理解.

[檔案頭 Metadata]
general.name = "MiniModel-v1"
general.description = "示範用微型語言模型"
general.file_type = 1 (FP32)
general.architecture = "Transformer"
general.quantization_version = 2

[tensors]
1. token_embedding.weight
   - shape: [5, 3]  # 5個token,每個token向量大小為3
   - data: [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], ...]

2. layer.0.attention.wq.weight
   - shape: [3, 2]  # 3維輸入 → 2維輸出
   - data: [[0.7, 0.8], [0.9, 1.0], [1.1, 1.2]]

3. layer.0.ffn.w1.weight
   - shape: [2, 4]
   - data: [[...], ...]

實際上 head -c 1000 granite-7b-lab-Q4_K_M.gguf | xxd -c 32, 可以看到檔案前面有幾個文字可讀, 後面 tensor 就都是 binary. 如果仔細看下圖, 會看到裡面還看到 ggml 的字樣, 那是 GGUF 的前代格式. 主要是差在舊版沒有 chunked index 結構, 不方便快速抓出某一層參數, 以及沒有 SHA 保護機制.

[REF]

  1. https://blog.csdn.net/shizheng_Li/article/details/144866526

DataLoader 和 Iterator 的差異

在 trace AI 課程中的訓練代碼時, 看到 iterator 和 DataLoader 同場出現時有點暈, 我想說這兩個做的事情不是一樣嗎? 所以花點時間將這個疑惑釐清.

先講結論: Iterator 是 design pattern, DataLoader 是 PyTorch 的 class.

Iterator 做為 design pattern 的好處是: 不用管 dataset 真正長什麼樣子, 每次都都抓出一包 data, 還用不到的先不抓, 等下次抓, 以節省記憶體.

那一包要抓多大呢? 透過 DataLoader 這個 class 取得 dataloader 這個 object.

然後 data_iterator 是個 iterator, iter() 把 dataloader 轉換成可迭代的 object.

first_batch 的 type 取決 Dataset, 主要就是一包 data. 而第一次呼叫 next() 拿到的是第一包, 而不是第二包.

from torch.utils.data import DataLoader, Dataset
...
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
....
data_iterator = iter(dataloader)
first_batch = next(data_iterator)

在 Tensorflow 中, 沒有 DataLoader, 但有 tf.data.Dataset. 效果也是一次抓一包.

import tensorflow as tf

# Create a basic dataset
dataset = tf.data.Dataset.range(10)

# Apply transformations (e.g., batch, shuffle)
batched_dataset = dataset.batch(2)

# Iterate through the dataset
for batch in batched_dataset:
    print(batch)