Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
- name: Install linting tools
run: |
pip install --upgrade pip
pip install black mypy
pip install black mypy ruff

- name: Check Python formatting with black
run: black --check python/ tests/ examples/
Expand All @@ -56,3 +56,7 @@ jobs:
- name: Run mypy type checking
run: mypy python/
continue-on-error: true

- name: Run ruff
run: ruff
continue-on-error: true
7 changes: 3 additions & 4 deletions examples/compare_quantization_sizes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"""

import tempfile
import os
import numpy as np
from pathlib import Path

Expand Down Expand Up @@ -122,9 +121,9 @@ def compare_sizes(size_multiplier=1):
print("=" * 80)
print("QUANTIZATION FILE SIZE COMPARISON")
print("=" * 80)
print(f"\nModel Configuration:")
print("\nModel Configuration:")
print(f" Base size: {64 * size_multiplier} x {64 * size_multiplier}")
print(f" Layers: 2 fully-connected layers (matmul + relu + matmul)")
print(" Layers: 2 fully-connected layers (matmul + relu + matmul)")
print(f" Total parameters: ~{2 * (64 * size_multiplier) ** 2:,}")
print()

Expand Down Expand Up @@ -226,7 +225,7 @@ def compare_sizes(size_multiplier=1):

if size_multiplier > 1:
print(f"\n[INFO] Using size multiplier: {size_multiplier}x")
print(f"[INFO] This will create larger models for more realistic comparison\n")
print("[INFO] This will create larger models for more realistic comparison\n")

results = compare_sizes(size_multiplier)

Expand Down
18 changes: 9 additions & 9 deletions examples/kv_cache_device_tensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def run_with_device_tensors(ctx, graph, num_steps=50):
past_key_device.write(np.zeros(kv_shape, dtype=np.float32))
past_value_device.write(np.zeros(kv_shape, dtype=np.float32))

print(f" Device tensors created:")
print(" Device tensors created:")
print(f" KV cache: {past_key_device}")

start = time.time()
Expand Down Expand Up @@ -177,7 +177,7 @@ def run_with_device_tensors(ctx, graph, num_steps=50):

except Exception as e:
print(f" Device tensor execution failed: {e}")
print(f" Note: This may be expected if device tensors are not fully supported yet")
print(" Note: This may be expected if device tensors are not fully supported yet")
return None


Expand All @@ -198,17 +198,17 @@ def main():
# Build decoder graph
print("Building decoder graph...")
graph = build_decoder_graph(ctx)
print(f" Graph built successfully")
print(" Graph built successfully")
print()

# Configuration
num_steps = 50

print(f"Configuration:")
print(f" Batch size: 1")
print(f" Sequence length: 128")
print(f" Hidden dim: 256")
print(f" Num heads: 4")
print("Configuration:")
print(" Batch size: 1")
print(" Sequence length: 128")
print(" Hidden dim: 256")
print(" Num heads: 4")
print(f" Decode steps: {num_steps}")
print()
print("-" * 70)
Expand Down Expand Up @@ -240,7 +240,7 @@ def main():
print("Note: Current implementation does host round-trips for both modes.")
print("True zero-copy execution will show larger speedups on GPU/NPU.")
else:
print(f"Device tensors: Not available")
print("Device tensors: Not available")
print()
print("This demo shows the API for device tensors.")
print("Full zero-copy execution coming soon!")
Expand Down
8 changes: 4 additions & 4 deletions examples/minilm_embeddings.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,16 +337,16 @@ def _load_webnn_model(self):
print(f"[INFO] Downloading model from Hugging Face Hub: {self.model_id}")
hub = webnn.Hub()
model_files = hub.download_model(self.model_id)
print(f"[OK] Model downloaded")
print("[OK] Model downloaded")

# Load graph from files
print(f"[INFO] Loading graph...")
print("[INFO] Loading graph...")
graph = webnn.MLGraph.load(
model_files['graph'],
manifest_path=model_files['manifest'],
weights_path=model_files['weights']
)
print(f"[OK] Graph loaded successfully")
print("[OK] Graph loaded successfully")
print(f" - Operand count: {graph.operand_count}")
print(f" - Operation count: {graph.operation_count}")
print(f" - Inputs: {graph.get_input_names()}")
Expand Down Expand Up @@ -677,7 +677,7 @@ def main():
except Exception as e:
error_msg = str(e)
if "404" in error_msg or "Entry Not Found" in error_msg:
print(f"[WARNING] Model not found on Hugging Face Hub")
print("[WARNING] Model not found on Hugging Face Hub")
print(
"[INFO] The model may not be uploaded yet to: tarekziade/all-MiniLM-L6-v2-webnn"
)
Expand Down
4 changes: 2 additions & 2 deletions examples/mobilenetv2_from_hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ def load_imagenet_labels():
# Download from GitHub if not found
import urllib.request
url = "https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt"
print(f" [DOWNLOAD] ImageNet labels from GitHub...")
print(" [DOWNLOAD] ImageNet labels from GitHub...")
urllib.request.urlretrieve(url, labels_file)
print(f" [OK] Labels downloaded")
print(" [OK] Labels downloaded")

with open(labels_file) as f:
return [line.strip() for line in f]
Expand Down
1 change: 0 additions & 1 deletion examples/mobilenetv2_real.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import time
import argparse
from pathlib import Path
import urllib.request

try:
import numpy as np
Expand Down
4 changes: 2 additions & 2 deletions examples/save_load_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def main():
save_path = "example_graph.webnn"
print(f"\n5. Saving graph to '{save_path}'...")
graph.save(save_path)
print(f" Graph saved successfully")
print(" Graph saved successfully")

# Check file size
file_size = os.path.getsize(save_path)
Expand Down Expand Up @@ -114,7 +114,7 @@ def main():
print(f"... ({len(content) - 500} more characters)")

# Cleanup
print(f"\n11. Cleaning up...")
print("\n11. Cleaning up...")
os.remove(save_path)
print(f" Removed '{save_path}'")

Expand Down
28 changes: 14 additions & 14 deletions examples/test_quantization_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,45 +45,45 @@ def test_quantization_roundtrip(quant_dtype="int8", quant_level_name="int8"):

# Quantize the input
quantized = builder.quantize_linear(x, scale, zero_point)
print(f" Created quantize_linear node")
print(f" Input dtype: float32")
print(" Created quantize_linear node")
print(" Input dtype: float32")
print(f" Output dtype: {quant_dtype}")

# Dequantize for computation
output = builder.dequantize_linear(quantized, scale, zero_point)
print(f" Created dequantize_linear node")
print(" Created dequantize_linear node")
print(f" Input dtype: {quant_dtype}")
print(f" Output dtype: float32")
print(" Output dtype: float32")

# Build graph
graph = builder.build({"output": output})
print(f" Graph built successfully")
print(" Graph built successfully")

# Step 2: Save with quantized=True marker
with tempfile.TemporaryDirectory() as tmpdir:
model_path = Path(tmpdir) / "quantized_model.webnn"
print(f"\n[STEP 2] Saving quantized graph to {model_path}...")
graph.save(str(model_path), quantized=True)
print(f" Model saved with quantized=True marker")
print(" Model saved with quantized=True marker")

# Verify file was created
if not model_path.exists():
print(f" [ERROR] Model file not found!")
print(" [ERROR] Model file not found!")
return False

# Read the file to verify quantized marker (JSON format uses "quantized": true)
print(f"\n[STEP 3] Verifying saved file contains quantized marker...")
print("\n[STEP 3] Verifying saved file contains quantized marker...")
with open(model_path, 'r') as f:
content = f.read()
if '"quantized": true' in content:
print(f" [OK] JSON quantized field found: \"quantized\": true")
print(" [OK] JSON quantized field found: \"quantized\": true")
else:
print(f" [ERROR] Quantized marker NOT found in saved file")
print(" [ERROR] Quantized marker NOT found in saved file")
print(f" First 500 chars: {content[:500]}")
return False

# Step 4: Run inference to verify it works
print(f"\n[STEP 4] Running inference...")
print("\n[STEP 4] Running inference...")
test_input = np.array([[1.0, 2.0, 3.0, 4.0],
[5.0, 6.0, 7.0, 8.0],
[9.0, 10.0, 11.0, 12.0],
Expand All @@ -104,11 +104,11 @@ def test_quantization_roundtrip(quant_dtype="int8", quant_level_name="int8"):
# So y should be close to x but with quantization error

if not np.allclose(output_tensor, test_input, rtol=0.1):
print(f" [WARNING] Output differs from input more than expected")
print(" [WARNING] Output differs from input more than expected")
else:
print(f" [OK] Output matches input (within quantization error)")
print(" [OK] Output matches input (within quantization error)")

print(f"\n[STEP 5] COMPLETE")
print("\n[STEP 5] COMPLETE")
print("=" * 80)
print()
return True
Expand Down
5 changes: 2 additions & 3 deletions examples/text_generation_enhanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
python text_generation_enhanced.py --weights model.json --use-kv-cache --tokens 100
"""

import sys
import argparse
import json
from pathlib import Path
Expand Down Expand Up @@ -187,8 +186,8 @@ def build_graph_with_kv(self, builder, x_input, cached_k, cached_v):
ln_beta = builder.constant(self.ln_beta, list(self.ln_beta.shape), "float32")

# Compute Q, K, V for new token
Q = builder.matmul(x_input, W_q) # (1, d_model)
K = builder.matmul(x_input, W_k) # (1, d_model)
builder.matmul(x_input, W_q) # (1, d_model)
builder.matmul(x_input, W_k) # (1, d_model)
V = builder.matmul(x_input, W_v) # (1, d_model)

# Simplified attention: use mean pooling
Expand Down
7 changes: 3 additions & 4 deletions examples/text_generation_gpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using pre-trained or initialized weights.
"""

import sys
import argparse
import json
from pathlib import Path
Expand Down Expand Up @@ -133,8 +132,8 @@ def build_graph(self, builder, x_input):

# Simplified attention: use last token's representation
# Q, K, V = x @ W_q, x @ W_k, x @ W_v
Q = builder.matmul(x_input, W_q) # (seq_len, d_model)
K = builder.matmul(x_input, W_k) # (seq_len, d_model)
builder.matmul(x_input, W_q) # (seq_len, d_model)
builder.matmul(x_input, W_k) # (seq_len, d_model)
V = builder.matmul(x_input, W_v) # (seq_len, d_model)

# Compute attention scores for last position
Expand Down Expand Up @@ -252,7 +251,7 @@ def main():
print("Next-Token Generation with Attention (WebNN)")
print("=" * 70)
print(f"Backend: {backend_name}")
print(f"Model: vocab=128 (ASCII), d_model=64, max_seq=32")
print("Model: vocab=128 (ASCII), d_model=64, max_seq=32")
print()

# Create WebNN context
Expand Down
1 change: 0 additions & 1 deletion examples/train_simple_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import argparse
import json
import numpy as np
from pathlib import Path


# Character-level vocabulary
Expand Down
6 changes: 3 additions & 3 deletions examples/train_text_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def compute_loss(model, X_batch, y_batch):

# Forward pass (simplified in NumPy)
# Q, K, V
Q = x_embedded @ model.W_q
K = x_embedded @ model.W_k
x_embedded @ model.W_q
x_embedded @ model.W_k
V = x_embedded @ model.W_v

# Mean pooling approximation
Expand Down Expand Up @@ -114,7 +114,7 @@ def train_step(model, X_batch, y_batch, lr=0.05):
Perform one training step with simple gradient descent.
Improved to focus on most important parameters.
"""
batch_size = X_batch.shape[0]
X_batch.shape[0]
epsilon = 0.0001

# Get original loss
Expand Down
2 changes: 1 addition & 1 deletion python/webnn/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Type stubs for webnn package"""

from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional
import numpy as np
import numpy.typing as npt

Expand Down
1 change: 0 additions & 1 deletion python/webnn/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
Downloads WebNN model files (graph, weights, manifest) from Hugging Face Hub.
"""

import os
from pathlib import Path
from urllib.request import urlretrieve
from urllib.error import HTTPError
Expand Down
2 changes: 0 additions & 2 deletions tests/test_coreml_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@
import argparse
import os
import sys
from pathlib import Path

import numpy as np

try:
import webnn
Expand Down
1 change: 0 additions & 1 deletion tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import argparse
import os
import sys
from pathlib import Path

import numpy as np

Expand Down
11 changes: 5 additions & 6 deletions tests/test_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from typing import Dict, List, Tuple

# Import conftest fixtures
from conftest import ml, context, builder


def measure_inference_time(context, graph, inputs: Dict, num_runs: int = 5) -> Tuple[float, List[float]]:
Expand All @@ -41,7 +40,7 @@ def measure_inference_time(context, graph, inputs: Dict, num_runs: int = 5) -> T

for i in range(num_runs):
start = time.time()
result = context.compute(graph, inputs)
context.compute(graph, inputs)
elapsed = (time.time() - start) * 1000
times.append(elapsed)

Expand Down Expand Up @@ -107,7 +106,7 @@ def test_performance_simple_onnx_cpu(ml):
warm_avg = sum(warm_times) / len(warm_times)

print(f"\n{'='*60}")
print(f"ONNX CPU - Simple Model (10 layers)")
print("ONNX CPU - Simple Model (10 layers)")
print(f"{'='*60}")
print(f"Cold start: {cold_time:.2f}ms")
print(f"Warm avg: {warm_avg:.2f}ms")
Expand Down Expand Up @@ -135,7 +134,7 @@ def test_performance_simple_coreml(ml):
warm_avg = sum(warm_times) / len(warm_times)

print(f"\n{'='*60}")
print(f"CoreML - Simple Model (10 layers)")
print("CoreML - Simple Model (10 layers)")
print(f"{'='*60}")
print(f"Cold start: {cold_time:.2f}ms")
print(f"Warm avg: {warm_avg:.2f}ms")
Expand All @@ -160,7 +159,7 @@ def test_performance_complex_onnx_cpu(ml):
warm_avg = sum(warm_times) / len(warm_times)

print(f"\n{'='*60}")
print(f"ONNX CPU - Complex Model (200 operations)")
print("ONNX CPU - Complex Model (200 operations)")
print(f"{'='*60}")
print(f"Cold start: {cold_time:.2f}ms")
print(f"Warm avg: {warm_avg:.2f}ms")
Expand Down Expand Up @@ -190,7 +189,7 @@ def test_performance_complex_coreml(ml):
speedup = cold_time / warm_avg

print(f"\n{'='*60}")
print(f"CoreML - Complex Model (200 operations)")
print("CoreML - Complex Model (200 operations)")
print(f"{'='*60}")
print(f"Cold start: {cold_time:.2f}ms")
print(f"Run 2: {warm_times[0]:.2f}ms")
Expand Down
Loading
Loading