np.add の追跡、すべてその先まで

2026/09/04 18:42

np.add の追跡、すべてその先まで

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

今回の分析の主な焦点は、NumPy 2.5.2 内の

np.add
の内部実行パスであり、速度と柔軟性のバランスの取り方を開示しています。この関数を呼び出した際、システムはまず Dask や CuPy などのライブラリから
__array_ufunc__
を通じてカスタムのオーバーライドがあるかを確認し、存在すればそれらがすぐに制御を引き継ぎます。オーバーライドが存在しない場合、NumPy は連続データに最適化された高速な直接的な C レベルのループを使うか、ブロードキャストを必要とする非連続入力に対してより複雑なイテレータシステムである
NpyIter
を使うかを判断します。このアーキテクチャは、NumPy のメインブランチで精査され、呼び出されるたびに繰り返しスキャンするのではなく、1 回の登録ごとにキャッシュにループ選択を移動させるように改良されています。これらの構造的な改善は、ライブラリを将来的なフリースレッド Python の並行性に対応させることを目指すとともに、AVX512 や NEON などの高度な CPU 機能を互換性を保ちながら実装します。その結果、サードパーティのライブラリの利用者は実行フローに対して即座に制御権を得られますが、HPC に依存する企業は、これらの専用 SIMD 要件により、最適化された連続配列とストライド操作の間にある顕著な性能ギャップを認識する必要があります。

Text to translate:

The primary focus of this analysis is the internal execution path of

np.add
in NumPy 2.5.2, revealing how it balances speed with flexibility. When you call this function, the system first checks for custom overrides from libraries like Dask or CuPy via
__array_ufunc__
, allowing them to take control immediately. If no override exists, NumPy decides whether to use fast, direct C-level loops optimized for contiguous data or a more complex iterator system called
NpyIter
for non-contiguous inputs that require broadcasting. This architecture is being refined on NumPy's main branch to move loop selection into a once-per-registration cache rather than scanning it repeatedly during every call. These structural improvements aim to prepare the library for future free-threaded Python concurrency while ensuring compatibility with advanced CPU features like AVX512 or NEON. Consequently, users of third-party libraries gain immediate control over execution flow, but companies relying on high-performance computing must recognize significant performance gaps between optimized contiguous arrays and strided operations due to these specialized SIMD requirements.

本文

np.add(a, b)
の旅:NumPy 内部構造の探求

NumPy の開発に携わり、「配列を加算する」というシンプルな操作が、Python の呼び出しから SIMD カーネルの実行まで、どのような仕組みで処理されているのかを追跡しました。

本記事では、NumPy 2.5.2 を基準として、

np.add(a, b)
(float64 配列)の呼び出しを起点に、ソースコードの追跡と理解を深めます。

注意: 本記事は NumPy 2.5.2 の内部構造に基づいています。バージョン間での変更がある場合は、該当するタグで独自に検証(spelunking)を行ってください。C の読解は必須ですが、事前の NumPy 知識はありません。


地図:呼び出しの階層

追跡の全体像を表した「宝地図」です。上位から下位へ分解されます。

  • np.add(a, b)
    • (Python: エントリーポイント)
  • ufunc_generic_fastcall
    • (C: 引数の解析・初期化)
  • __array_ufunc__ オーバーライドチェック
    • (サードパーティライブラリへの分岐判定)
  • プロモーションとディスパッチ
    • (float64 ループの探索、キャッシュ化)
  • 単純なループ
    または
    NpyIter
    • (反復戦略:高速パスかイテレーター生成か)
  • DOUBLE_add
    • (実際の内部ループ、SIMD 対応)

1.
np.add
はオブジェクトである

np.add
は通常の Python 関数ではなく、
numpy.ufunc
のインスタンス
です。これは C で定義された型であり、内部では「入出力の束」を管理しています。

>>> type(np.add)
<class 'numpy.ufunc'>
>>> np.add.nin, np.add.nout
(2, 1)          # 入力数:2, 出力数:1
>>> len(np.add.types)
22              # 登録された型シグネチャの数
>>> np.add.types[11:14]
['ee->e', 'ff->f', 'dd->d']
  • ufunc(通用関数)の本質:
    • 対応する型シグネチャごとに 1 つの C 関数。
    • メタデータ(入出力数など)を保持。
  • 追跡対象:
    dd->d
    (double 入力 → double 出力)。

2. C への潜入:ufunc_generic_fastcall

Python が

np.add(a, b)
を呼んだ際、
ufunc_generic_fastcall
という関数にフォワードされます。この関数は全体の骨格です(要約版)。

static PyObject *
ufunc_generic_fastcall(PyUFuncObject *ufunc, ...) {
    // 1. 入力・出力・キーワード引数を抽出
    
    // 2. オーバーライドチェック (`__array_ufunc__` など)
    //    サードパーティライブラリ(Dask, CuPy)のフック判定
    
    // 3. 引数→配列に変換、DTyes(データ型)を抽出
    
    // 4. ディスパッチとプロモーションの実行
    PyArrayMethodObject *ufuncimpl = promote_and_get_ufuncimpl(...);
    
    // 5. デスクリプタの解決
    
    // 6. 最終処理:内部ループへの実行
    errval = PyUFunc_GenericFunctionInternal(ufunc, ufuncimpl, ...);
    
    // 7. 結果をラップして返す
}

3. オーバーライドチェックとサードパーティの介入

NumPy は実際に計算を行う前に、引数が**「自分で行うべきこと」**がないか確認します。

  • __array_ufunc__
    プロトコル
    :
    • NEP 133 で定義されたメソッド。
    • Dask や CuPy などのライブラリが主導権を握るためのフック。
  • 動作例:
    class Diverted:
        def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
            return f"intercepted {ufunc.__name__}.{method}"
    
    >>> np.add(np.arange(3), Diverted())
    'intercepted add.__call__'
    

通常の配列(ndarray)の場合、このチェックを通過して計算権限を維持します。


4. ループの選択:プロモーションとディスパッチ

2 つの

float64
配列が渡されたとき、**「dd->d」**という型シグネチャを持つループを見つける必要があります(
dispatching.cpp
)。

ディスパッチステップ

  1. オーバーライド適用:
    signature
    の固定があるか確認。
  2. キャッシュチェック: 同じ組み合わせで呼ばれた場合はキャッシュヒット。
  3. マルチディスパッチ: 最適な「ループ」を探す(サブクラスを持つ DType)。
  4. プロモーター: マッチしない場合、型を変換して再試行するヘルパー。
  5. 登録確認:
    ArrayMethod
    が発見され、ufunc ループに登録される。

クラシックなループへのアダプター

新しいインターフェース (

PyArrayMethodObject
) から、実際の C 関数ポインタ (
PyUFuncGenericFunction
) に到達するための「つばさ」です。

static int
generic_wrapped_legacy_loop(PyArrayMethod_Context *context, ...) {
    legacy_array_method_auxdata *ldata = (legacy_array_method_auxdata *)auxdata;
    
    // クラシックなループインターフェースへの呼び出し
    ldata->loop(data, dimensions, strides, user_data); 
    
    return 0;
}

5. 反復するか?:高速パスと NpyIter

実際に計算を行う前に、「単純なループ(trivial loop)」を使うべきかを判断します。

高速パス (
try_trivial_single_output_loop
)

条件を満たせば、イテレーター (

NpyIter
) を構築せず、1 回だけでデータを処理します。

  • 条件:
    • 形状が一致し、ブロードキャスト不要。
    • キャスト不要。
    • オペランドがすべて 1-D または連続(contiguous)。
  • 動作: 単一の C 関数呼び出しで完了。

一般パス (
execute_ufunc_loop
+
NpyIter
)

高速パスの条件に合わなければ、一般的な配列イテレーターを使用します。

npy_uint32 iter_flags = ufunc->iter_flags |
             NPY_ITER_EXTERNAL_LOOP | // イテレーター管理
             NPY_ITER_REFS_OK | 
             NPY_ITER_BUFFERED |      // ブロードキャスト対応など
             ...;
             
// メインループ
do {
    res = strided_loop(context, dataptr, countptr, strides, auxdata);
} while (res == 0 && iternext(iter)); // 次のスライスへ進む
  • GIL の解放: ループ中は GIL を解放し、マルチスレッド/プロセス環境で並行性を確保。
  • 浮動小数点フラグ: 計算前後にクリアされ、オーバーフローなどを検出。

ストライドとメモリの関係

ストライド(stride)がある場合でも、1-D であれば高速パスが可能です。ただし、不連続な N-D ビューや

out
パラメータのオーバーラップがある場合はイテレーターが生成されます。

ベンチマーク例:

import numpy as np
import timeit

n = 10_000_000
a, b, out = (np.random.rand(2 * n) for _ in range(3))
contig_a, contig_b, contig_out = (x[:n].copy() for x in (a, b, out))
strided_a, strided_b, strided_out = (x[::2] for x in (a, b, out))

time_contig = timeit.timeit("np.add(contig_a, contig_b)", globals=globals(), number=10)
time_strided = timeit.timeit("np.add(strided_a, strided_b)", globals=globals(), number=10)
  • 結果: 連続配列が約 2.6 倍速い。
  • 理由: ストライド版はメモリ帯域幅を 2 倍消費するためだが、根本的な差は
    DOUBLE_add
    の内部実装にある。

6. ループそれ自身:SIMD と特殊化

DOUBLE_add
という関数はリポジトリには存在せず、テンプレートからコード生成されています(
loops_arithm_fp.dispatch.c.src
)。

コード生成の仕組み

/**begin repeat
 * Float types: #type = npy_double#
 * Arithmetic: # kind = add#
 */
NPY_NO_EXPORT void NPY_CPU_DISPATCH_CURFX(DOUBLE_add)
(char **args, ...) {
    // 内部実装は CPU 依存で変化する
}
  • conv_template.py
    がテンプレートを展開し、複数の C 関数(
    FLOAT_add
    ,
    DOUBLE_add
    など)を生成。

特殊化の階層

  1. 還元ケース: 結果への書き込みが必要ない場合。
  2. SIMD 加速版:
    • 条件: 両方の入力が連続、または片方がスカラー。
    • 実装:
      npyv_add_f64
      (CPU 固有の SIMD 命令:AVX, NEON など)。
  3. フォールバック (Scalar):
    • シグナル化されない場合の単純なループ。

ストライド呼び出しの性能低下理由:

  • SIMD 特殊化は「オペランドが連続」であることを条件としている。
  • ストライド配列はこれを満たさないので、**スカラーブランチ(単純な for ループ)**にフォールバックする。
  • これにより、SIMD の恩恵を受けず、メモリ帯域幅も効率的でないため低速になる。

7. ループはどこから来るか:動的生成と CPU チェック

生成された関数 (

DOUBLE_add
) がどのように
ufunc->functions
テーブルに格納されるかというと、コード生成の結果です。

ディスパッチテーブルの構成

  • 生成元:
    generate_umath.py
    (Python) →
    __umath_generated.c
    (C)。
  • 構造例:
    'add': {
        'dd->d': {
            dispatch=[('loops_arithm_fp', 'fdFD')], # 浮動小数点/複素数系
        },
    }
    
  • ビルド時: マクロで CPU 機能をチェックし、最適なループを選択。
    NPY_CPU_DISPATCH_CALL(func = add);
    // X86: AVX512 -> AVX2 -> Baseline
    // ARM : NEON (条件付き)
    
  • 確認方法:
    >>> from numpy.lib import introspect
    >>> introspect.opt_func_info("add", "float64")
    # CPU 依存の結果を表示(例: 'baseline(NEON ...)')
    

8. 結論:地質学としての NumPy

np.add(a, b)
の呼び出しは、以下の複雑なプロセスを経ます。

  1. オブジェクト解析:
    ufunc
    オブジェクトの確認とオーバーライドチェック。
  2. 型解決: データ型 (
    float64
    ) を特定し、キャッシュされたループを探索。
  3. 戦略選択: 配列の性質(連続性など)から高速パスかイテレーター生成かを選択。
  4. 実行: GIL 解放を行い、CPU の能力に応じた SIMD ループを実行。

技術的インサイト

  • レガシーの層:
    legacy_array_method.c
    など古いインターフェースがラップされながらも、今も主力として稼働している。
  • 進化の痕跡: 型システムや SIMD 対応など、数十年前のコードの上に新しいレイヤーが積み重ねられ、かつ内部でも継続的に改良されている(例:ハッシュテーブル化されたループ選択)。
  • 動機: この探求は、NumPy を「スレッドフリー Python」への移行などの大きな変化を予感させる。

おわりに: 単純な加算操作が、これほど多くの設計と最適化を内包していることは驚異的です。より深い理解のために、ブレークポイントを置いたソースコード追跡をお試しください。

同じ日のほかのニュース

一覧に戻る →

2026/09/08 23:55

Google DeepMind、AlphaGenome アトラスをリリース

## Japanese Translation: ## サマリー: Google DeepMind は、ヒトゲノムにおけるあらゆる可能な単一ヌクレオチド変異の影響を予測することを目的とした画期的なツール「AlphaGenome Atlas」を導入しました。この 1 ペタバイト規模の巨大データセットは、全ての 90 億個の可能性のある変化について事前に影響を計算しており、「AVI スコア」というスコアを用いてどの遺伝的変異が最も重要かを瞬時に優先順位付けします。この革新は、稀少疾患の原因特定という重要な課題に対処しており、例えば広域研究所(Broad Institute)で同定された *DNM1* 遺伝子の特定の突然変異によって誤ったスプライスサイトが作成される問題に対処しています。さらに、54,000 人以上の UK Biobank 参加者のデータに適用した際、アトラスは非コード領域と身体質量指数(BMI)との新しい関連性を発見しました。予測された分子効果に基づいて変異をグループ化することで、従来の手法よりも 22% も多くの関連性を明らかにし、BMI に関連する 19 の遺伝的領域を同定しました。複雑で高度なプログラミングスキルを必要とする以前の手法とは対照的に、このツールは直感的なウェブサイトポータルを通じてアクセス可能となり、世界の臨床研究者へのアクセスを民主化しています。結局のところ、アトラスは科学者が膨大な技術的専門知識を必要とせずに最も有望な遺伝的なリードにリソースを集中させることを可能にし、発見のスピードを加速させています。

2026/09/09 5:07

MacBook Pro で 4 つの SSD からストリーミングし、トークン速度が 1 トークン/秒の Kimi K3(2.8T)

## 日本語翻訳: 元のサマリーは読みやすいですが、Key Points List に含まれる数量的な深みに欠け、欠落している指標なしでは具体的なパフォーマンスに関する主張を評価することが困難です。以下の改良版は、重要なデータポイントを取り入れつつ文脈の流れを維持したものです: ## サマリー このプロジェクトは、ARGODRIVE の Deltafin フォークを使用して、アップストリーム仕様に従い(専門家プリューニングやウェイトの減少など、品質を犠牲にするショートカットを導入しない)、Apple M5 Max MacBook Pro(128 GB RAM)上で 2.8T パラメータの Kimi K3 MoE モデルをフルスケールで展開することを実証しています。**パフォーマンス分析**によると、デコードスループットはプロンプト長に応じて約 0.96〜1.13 tokens/s(アップストリームの約 0.68 tokens/s)と安定しています。しかし、このセットアップには 512 トークンのプロンプトに対する「最初のトークンまでの時間」(Time to First Token)の遅延が約 375 秒と大きく、これはプリフィル中に特定のメモリ専門家を読み直す効率が悪い(具体的には各レイヤーの専門家を 8 倍読み返す)ことが原因です。SSD スケールは対数曲線に従い、最も低速なレイヤーがペースを規定します。ドライブを追加しても収益性が低下し、1 ドライブがベースラインである 4 ドライブの約 52%、2 つのミラーリングが約 73%、3 ドライブが約 90% を提供します。このプロジェクトは、精度を犠牲にして(例えば専門家のウェイトを約 3 bit に減少させるなど)人工的に速度を上げる他のイニシアチブと区別されます。ユーザーは `--full` モード(1.7 TB のローカルダウンロード)または `--stream`(オンデマンドキャッシング)のいずれかを選択できます。デフォルトでは Inferact の Kimi-K3-DSpark が含まれており、生テキストタスク用にはオプションの Qwen アクセラレーラーが用意されています(チャット速度ではありません)。コードは MIT ライセンスで公開され、Moonshot AI と何らの関係もありません。アップグレードには `cargo build` を通じた手動メンテナンスが必要であり、アップストリームのライセンス条項に従っています。将来の計画は、プリフィルの読み直し問題を解決して起動遅延を排除することに焦点が当てられています。

2026/09/09 6:47

大規模言語モデルが適応的探索によって新たな社会的バイアスを発明する

## Japanese Translation: OpenReview にアクセスする前に、新規ユーザーは作業を続行するには強制のセキュリティ検証手順を完了する必要があります。既存のアカウント保有者は直接ログインすることでこの要件を回避でき、これにより自動的にメインインターフェースに遅延なくリダイレクトされます。その結果、新規ユーザーはチェックを完了させるまでワークフローを一時的に停止する必要があり、登録済みアカウント向けの免除を利用しない限りです。組織が新しい人材オンボーディングを行う場合、これらの個人が認証を完了する間、一時的な摩擦が発生する可能性があります。したがって、このプラットフォームは新規参入者に対して厳格な入場を強制し、一方で検証済みのユーザーにはシームレスなアクセスを維持します。

np.add の追跡、すべてその先まで | そっか~ニュース