llmfitは「手元のGPU/CPUでどのLLMが動くか」を一発で診断するCLIツールです(k8sgptの作者による小粒プロジェクト)。実体はGoではなくRustで書かれており、中核のfit.rsには「モデルの速度・メモリ適合度をどう見積もるか」の計算ロジックが詰まっています。今回は、実行モード(GPU実行かCPUオフロードか等)や用途別の重み付けスコアを「設定可能だがデフォルト値も持つ」形で表現している部分を読みます。Rustの構造体とDefaultトレイトの組み合わせという、実務でとてもよく出るパターンです。
コード
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct RunModeFactors {
pub gpu: f64,
pub tensor_parallel: f64,
pub moe_offload: f64,
pub cpu_offload: f64,
pub cpu_only: f64,
}
impl Default for RunModeFactors {
fn default() -> Self {
Self {
gpu: 1.0,
tensor_parallel: 0.9,
moe_offload: 0.8,
cpu_offload: 0.5,
cpu_only: 0.3,
}
}
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct ScoringWeights {
/// (quality_weight, speed_weight, fit_weight, context_weight) per use case,
/// stored in the same order as `UseCase` variants.
/// Order: General, Coding, Reasoning, Chat, Multimodal, Embedding
pub weights: [[f64; 4]; 6],
}
impl Default for ScoringWeights {
fn default() -> Self {
Self {
weights: [
[0.45, 0.30, 0.15, 0.10], // General
[0.50, 0.20, 0.15, 0.15], // Coding
[0.55, 0.15, 0.15, 0.15], // Reasoning
[0.40, 0.35, 0.15, 0.10], // Chat
[0.50, 0.20, 0.15, 0.15], // Multimodal
[0.30, 0.40, 0.20, 0.10], // Embedding
],
}
}
}
引用はリポジトリの実物と機械で照合しています。「// …略…」は省略した行です。
上から順に読む
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]`#[...]` はRustの「属性(attribute)」という構文で、この直下の要素(ここでは次の構造体)にコンパイラへの追加指示を与えます。`derive(...)`は「このトレイト(振る舞いの契約)の実装を自動生成してね」という意味です。Debugは`{:?}`で中身を出力できるようにする、Cloneは複製メソッド`.clone()`を生やす、Copyは代入時に所有権を奪わずビット単位でコピーする軽量な型にする、serde::Serialize/Deserializeは外部crate(サードパーティライブラリ)のserdeが提供するJSON等への変換機能です。1行書くだけでこれら5つの機能が手書きなしで手に入ります。
pub struct RunModeFactors {
pub gpu: f64,
pub tensor_parallel: f64,
pub moe_offload: f64,
pub cpu_offload: f64,
pub cpu_only: f64,
}`struct` は複数のデータをひとまとめにする型定義(Goのstructと同じ発想)です。`pub` はpublicの略で、このモジュールの外からも見える/使えることを示します。フィールドはすべて`f64`(64ビット浮動小数点数)型で、GPUで実行する場合・テンソル並列で実行する場合・MoEオフロード・CPUオフロード・CPUのみ、という5つの実行モードそれぞれに対する「速度の掛け目(倍率)」を保持しています。
impl Default for RunModeFactors {
fn default() -> Self {`impl Trait for Type` はRustで「この型にこのトレイトを実装する」ための構文です。`Default`は標準ライブラリのトレイトで、「引数なしで妥当な初期値を作れる型」という契約を表します。`fn default() -> Self` はそのトレイトが要求するメソッドの実装で、`Self`は「今implしている型自身(ここではRunModeFactors)」を指すエイリアスです。この実装があると、他のコードから`RunModeFactors::default()`や`Default::default()`で初期値を取得できるようになります。
Self {
gpu: 1.0,
tensor_parallel: 0.9,
moe_offload: 0.8,
cpu_offload: 0.5,
cpu_only: 0.3,
}`Self { field: value, ... }` は構造体リテラルで、各フィールドに値を詰めてインスタンスを作る構文です(Rustではこれが関数の最後の式ならセミコロンなしでそのまま戻り値になります)。数値の意味を見ると、GPUで動かす場合は基準の1.0倍速、テンソル並列は0.9倍、MoEオフロードは0.8倍…とCPUのみの0.3倍まで段階的に下がっています。つまりこの構造体は「実行方式が理想から離れるほど遅くなる」というドメイン知識を、コード上は単なる5つの数値として表現しているわけです。
pub struct ScoringWeights {
/// (quality_weight, speed_weight, fit_weight, context_weight) per use case,`///` はドキュメントコメントで、`//`の普通のコメントと違いこの構文で書くと`cargo doc`が拾ってAPIドキュメントとして表示してくれます。ここでは「weightsフィールドの各要素は(品質, 速度, 適合度, コンテキスト長)の重みの並びだよ」という意味をコードのすぐ上に書き残しています。コメントも含めてコードの一部として大切に扱うRustらしい書き方です。
/// Order: General, Coding, Reasoning, Chat, Multimodal, Embedding
pub weights: [[f64; 4]; 6],型`[[f64; 4]; 6]`は「固定長配列の配列」です。`[f64; 4]`が「f64が4個入った配列」で、それをさらに6個並べたものが全体の型になります。直前のコメント行`Order: General, Coding, ...`が、6個並ぶ配列の何番目がどの用途(一般・コーディング・推論など)に対応するかを示しており、コードだけでは読み取れない「順序の意味」をコメントで補っています。
[0.45, 0.30, 0.15, 0.10], // General
[0.50, 0.20, 0.15, 0.15], // Coding配列リテラルは`[要素, 要素, ...]`と書きます。ここでは内側の`[0.45, 0.30, 0.15, 0.10]`のような配列が外側の`[`の中に6個並んでいます。行末の`// General`のようなコメントは、上のドキュメントコメントで書かれた「Order: General, Coding, ...」という順番と対応させて、読み手がその場で「これが何番目の用途の重みか」を迷わず分かるようにする工夫です。各行を合計するとだいたい1.0(0.45+0.30+0.15+0.10)になっており、4つの評価軸への配分になっていることも読み取れます。