stMind

about Tech, Computer vision and Machine learning

ollama psの動作

ollama psを使うと、現在メモリにロードされているモデルを確認することが出来る。

NAME             ID              SIZE    PROCESSOR   UNTIL              
llama3.1:latest 91ab477bec9d    6.7 GB  100% GPU    4 minutes from now

ollama psの動作をトレース

ollama psは以下のメソッドにより実行される。

psCmd := &cobra.Command{
    Use:     "ps",
    Short:   "List running models",
    PreRunE: checkServerHeartbeat,
    RunE:    ListRunningHandler,
}

ollama runと同様に、checkServerHeartbeatでサーバーが起動されているかを確認し、ListRunningHandlerにおいてサーバーにロードされるモデルの情報を取得、表示する。

ListRunningHandlerは50行ほどのメソッドなので、ここに貼り付けてみます。

func ListRunningHandler(cmd *cobra.Command, args []string) error {
    client, err := api.ClientFromEnvironment()
    if err != nil {
        return err
    }

    models, err := client.ListRunning(cmd.Context())
    if err != nil {
        return err
    }

    var data [][]string

    for _, m := range models.Models {
        if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
            var procStr string
            switch {
            case m.SizeVRAM == 0:
                procStr = "100% CPU"
            case m.SizeVRAM == m.Size:
                procStr = "100% GPU"
            case m.SizeVRAM > m.Size || m.Size == 0:
                procStr = "Unknown"
            default:
                sizeCPU := m.Size - m.SizeVRAM
                cpuPercent := math.Round(float64(sizeCPU) / float64(m.Size) * 100)
                procStr = fmt.Sprintf("%d%%/%d%% CPU/GPU", int(cpuPercent), int(100-cpuPercent))
            }
            data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), procStr, format.HumanTime(m.ExpiresAt, "Never")})
        }
    }

    table := tablewriter.NewWriter(os.Stdout)
    table.SetHeader([]string{"NAME", "ID", "SIZE", "PROCESSOR", "UNTIL"})
    table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
    table.SetAlignment(tablewriter.ALIGN_LEFT)
    table.SetHeaderLine(false)
    table.SetBorder(false)
    table.SetNoWhiteSpace(true)
    table.SetTablePadding("\t")
    table.AppendBulk(data)
    table.Render()

    return nil
}

モデルの情報を問い合わせているのは、client.ListRunning(cmd.Context())のところです。

// ListRunning lists running models.
func (c *Client) ListRunning(ctx context.Context) (*ProcessResponse, error) {
    var lr ProcessResponse
    if err := c.do(ctx, http.MethodGet, "/api/ps", nil, &lr); err != nil {
        return nil, err
    }
    return &lr, nil
}

/api/psにGETリクエストを送り、レスポンスを受け取ります。デフォルトのサーバーでは、127.0.0.1:11434となっているので(OLLAMA_HOST環境変数で指定できる)、curlでGETしてみると、次のような内容が返ってきます。

{
  "models": [
    {
      "name": "llama3.1:latest",
      "model": "llama3.1:latest",
      "size": 6654289920,
      "digest": "91ab477bec9d27086a119e33c471ae7afbd786cc4fbd8f38d8af0a0b949d53aa",
      "details": {
        "parent_model": "",
        "format": "gguf",
        "family": "llama",
        "families": [
          "llama"
        ],
        "parameter_size": "8.0B",
        "quantization_level": "Q4_0"
      },
      "expires_at": "2024-09-07T21:28:58.954316925+09:00",
      "size_vram": 6654289920
    }
  ]
}

expires_atは例えば4 minutes from nowに変換され、size == size_vramの場合には100% GPUと表示される。また、IDはdigest[:12]が使われる。結果として、最初のような表示が行われることになる。

メモリサイズをどうやって計算しているかを知りたいと思ったが、そのためにはサーバー側の処理も読む必要がありそう。