Delete README.md
Browse files
README.md
DELETED
|
@@ -1,271 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
license: openrail++
|
| 3 |
-
pipeline_tag: text-to-image
|
| 4 |
-
language:
|
| 5 |
-
- en
|
| 6 |
-
tags:
|
| 7 |
-
- onnx
|
| 8 |
-
- onnxruntime
|
| 9 |
-
- webgpu
|
| 10 |
-
- stable-diffusion
|
| 11 |
-
- quantized
|
| 12 |
-
- browser
|
| 13 |
-
inference: false
|
| 14 |
-
---
|
| 15 |
-
|
| 16 |
-
# NanoDiffuser
|
| 17 |
-
|
| 18 |
-
<p align="center">
|
| 19 |
-
<img src="./NDLGO2k.png" alt="NanoDiffuser logo" width="360" />
|
| 20 |
-
</p>
|
| 21 |
-
|
| 22 |
-
**One-step text-to-image diffusion, running locally in your browser.**
|
| 23 |
-
|
| 24 |
-
NanoDiffuser uses a **custom-modified version of Stable Diffusion, adapted and quantized to fit a tight sub-400 MiB model budget**. Compact ONNX graphs, a JavaScript inference pipeline, and a minimal interface generate 512 × 512 images using the visitor's GPU through WebGPU. Hosting serves static files; image generation requires no inference server, API key, or server-side GPU.
|
| 25 |
-
|
| 26 |
-
The modifications cover model conversion, mixed-precision quantization, browser compatibility, local inference, and static deployment. No additional training or fine-tuning was performed for this package. The approximately 382 MiB budget covers the inference graphs, weights, tokenizer, and configuration; JavaScript and WebAssembly assets are additional. In decimal units, the model assets are approximately 400.5 MB.
|
| 27 |
-
|
| 28 |
-
## At a glance
|
| 29 |
-
|
| 30 |
-
| Property | Configuration |
|
| 31 |
-
| --- | --- |
|
| 32 |
-
| Base | Custom-modified Stable Diffusion adapted for a sub-400 MiB model budget |
|
| 33 |
-
| Output | One 512 × 512 RGB image, downloadable as PNG |
|
| 34 |
-
| Sampling | One denoising step, classifier-free guidance disabled |
|
| 35 |
-
| Scheduler | Exported one-step DEIS coefficients |
|
| 36 |
-
| Text encoder | CLIP, asymmetric Q4 MatMul/Gather weights |
|
| 37 |
-
| Denoiser | U-Net, Q4 matrix weights + per-channel INT8 convolution storage, with FP16 retained where needed |
|
| 38 |
-
| Decoder | TAESD / `AutoencoderTiny`, FP16 |
|
| 39 |
-
| Model assets | Approximately 382 MiB, excluding JavaScript and WebAssembly runtime assets |
|
| 40 |
-
| Execution | ONNX Runtime Web with WebGPU; tokenization and orchestration in JavaScript |
|
| 41 |
-
| Hosting | Static HTML, CSS, JavaScript, WebAssembly, and model files |
|
| 42 |
-
|
| 43 |
-
## Getting started
|
| 44 |
-
|
| 45 |
-
Open NanoDiffuser in a browser that exposes WebGPU and the `shader-f16` feature. The interface checks these capabilities before enabling generation. Chrome or Edge with hardware acceleration enabled are the suggested starting point; support also depends on the GPU, driver, operating system, and browser configuration.
|
| 46 |
-
|
| 47 |
-
1. Enter a prompt or choose an example.
|
| 48 |
-
2. Optionally open **Settings** to choose a fixed seed. Random seeds are enabled by default.
|
| 49 |
-
3. Select **Generate image**. On the first run, the application downloads the model assets and loads the inference runtime. Progress shows the download and generation stages.
|
| 50 |
-
4. Use **Download** to save the result as a PNG. The displayed seed belongs to that image.
|
| 51 |
-
|
| 52 |
-
You can also use **Settings → Choose folder** to select an extracted local model package containing `manifest.json`, all three ONNX graphs, their external `.data` files, and the tokenizer files. Selecting a folder reads its files locally.
|
| 53 |
-
|
| 54 |
-
The exported sequence length is **77 CLIP tokens**, including special tokens. Longer prompts are truncated during tokenization even though the text box accepts up to 1,000 characters. Resolution, batch size, sampling steps, and guidance are fixed; this interface does not provide negative prompts or image-to-image generation.
|
| 55 |
-
|
| 56 |
-
## How generation works
|
| 57 |
-
|
| 58 |
-
```text
|
| 59 |
-
Prompt → CLIP tokenizer → Q4 CLIP encoder → FP16 text embeddings
|
| 60 |
-
Seed → Gaussian noise → one-step U-Net → scheduler update
|
| 61 |
-
↓
|
| 62 |
-
FP16 TAESD → pixels → canvas → PNG
|
| 63 |
-
```
|
| 64 |
-
|
| 65 |
-
The tokenizer runs locally through Transformers.js, pads or truncates the prompt to 77 tokens, and supplies an INT32 `[1, 77]` tensor. CLIP produces `[1, 77, 768]` text embeddings, which the browser converts to FP16 for the U-Net.
|
| 66 |
-
|
| 67 |
-
A seeded JavaScript random generator and Box–Muller transform create Gaussian noise with shape `[1, 4, 64, 64]`. The U-Net receives the scaled noise, text embeddings, and timestep `999`. Its prediction is combined with the initial latent using coefficients saved from the one-step DEIS scheduler:
|
| 68 |
-
|
| 69 |
-
```text
|
| 70 |
-
initial_latent = noise × initNoiseSigma
|
| 71 |
-
model_input = initial_latent × modelInputScale
|
| 72 |
-
denoised = sampleCoefficient × initial_latent
|
| 73 |
-
+ outputCoefficient × noise_prediction
|
| 74 |
-
```
|
| 75 |
-
|
| 76 |
-
The current manifest contains:
|
| 77 |
-
|
| 78 |
-
```json
|
| 79 |
-
{
|
| 80 |
-
"timestep": 999.0,
|
| 81 |
-
"initNoiseSigma": 1.0,
|
| 82 |
-
"modelInputScale": 1.0,
|
| 83 |
-
"sampleCoefficient": 14.642590522766113,
|
| 84 |
-
"outputCoefficient": -14.579278945922852
|
| 85 |
-
}
|
| 86 |
-
```
|
| 87 |
-
|
| 88 |
-
These values are part of this export's sampling contract. The browser uses the recorded values directly. It does not reconstruct Diffusers' scheduler internals or apply an extra classifier-free guidance pass.
|
| 89 |
-
|
| 90 |
-
The denoised latent is converted to FP16 and passed to TAESD. The decoder wrapper maps its output into `[0, 1]`; JavaScript converts the channel-first output into 8-bit RGBA pixels for the canvas and PNG export.
|
| 91 |
-
|
| 92 |
-
Fixed seeds make the JavaScript noise sequence repeatable. They do not imply pixel-identical results to PyTorch, which uses a different random generator, or across different browser/GPU implementations.
|
| 93 |
-
|
| 94 |
-
## Conversion and quantization
|
| 95 |
-
|
| 96 |
-
The supplied `sdxs512_webgpu_colab.ipynb` exports three separate graphs using PyTorch's ONNX exporter, evaluation mode, constant folding, and fixed dimensions. Export starts with ONNX opset 17; the U-Net is subsequently set to opset 21 for its FP16 dequantization operations.
|
| 97 |
-
|
| 98 |
-
| Component | Conversion | Graph + external weights |
|
| 99 |
-
| --- | --- | ---: |
|
| 100 |
-
| CLIP | FP32 export; asymmetric Q4 weight-only quantization of eligible MatMul and Gather operations | 63.36 MiB |
|
| 101 |
-
| U-Net | FP16 export; mixed Q4 matrix weights and per-output-channel INT8 convolution storage | 312.71 MiB |
|
| 102 |
-
| TAESD | FP16 decoder export | 2.36 MiB |
|
| 103 |
-
|
| 104 |
-
Q4 quantization uses ONNX Runtime's weight-only quantizer with **block size 128**, asymmetric zero-points, `QOperator` format, and `accuracy_level=2`. Quantized matrix multiplication uses packed `MatMulNBits` weights. The graph checks require packed UINT8 zero-points.
|
| 105 |
-
|
| 106 |
-
U-Net convolution weights are quantized per output channel to signed INT8, with FP16 scales and a `DequantizeLinear` node before convolution. **INT8 reduces weight storage; the convolution still executes in floating point.** Input/output convolutions and timestep-projection MatMuls are excluded from their respective quantization passes and retain FP16 weights.
|
| 107 |
-
|
| 108 |
-
The U-Net uses Diffusers' explicit `AttnProcessor` during export. TAESD is loaded from the source checkpoint's `vae` subfolder; the export includes its decoder path.
|
| 109 |
-
|
| 110 |
-
Compatibility handling includes:
|
| 111 |
-
|
| 112 |
-
- Keeping CLIP export and LayerNormalization parameters in FP32, then converting final embeddings to FP16 at the U-Net boundary.
|
| 113 |
-
- A repair utility for older exports with mixed FP32/FP16 LayerNormalization parameters: `test-site/scripts/repair_text_encoder_layernorm.py`.
|
| 114 |
-
- Normalizing reduction axes into attributes or inputs according to the graph's opset.
|
| 115 |
-
- Using U-Net opset 21 so FP16 dequantization scales produce weights compatible with FP16 convolution inputs.
|
| 116 |
-
- Removing unreferenced initializers and consolidating external weights into one `.data` file per graph.
|
| 117 |
-
- Saving the tokenizer, fixed tensor shapes, filenames, and measured scheduler coefficients alongside the weights.
|
| 118 |
-
|
| 119 |
-
The Colab notebook is configured for a CUDA GPU, such as a T4. Its installation cell requests a session restart before export. Conversion can use several gigabytes of temporary disk space; the final release is much smaller. This conversion introduces no new training dataset.
|
| 120 |
-
|
| 121 |
-
## Execution, memory, and caching
|
| 122 |
-
|
| 123 |
-
The browser pipeline uses `onnxruntime-web/webgpu`, NCHW layout, and ONNX Runtime graph optimization set to `all`. The current JavaScript dependency versions are ONNX Runtime Web **1.29.0** and Transformers.js **4.2.0**. The source checkout's lockfile records the complete dependency versions.
|
| 124 |
-
|
| 125 |
-
Both the capability check and inference initialization request `powerPreference: "high-performance"`. This asks the browser to prefer a discrete GPU, such as an NVIDIA RTX, on systems with integrated and discrete graphics. It is a preference; the browser and operating system retain the final selection. See the [WebGPU adapter selection reference](https://gpuweb.github.io/types/interfaces/GPURequestAdapterOptions.html).
|
| 126 |
-
|
| 127 |
-
CLIP, U-Net, and TAESD sessions are created, run, and released sequentially to reduce peak GPU memory. Intermediate tensors are disposed when no longer needed. Model files can remain in browser memory, while GPU sessions are recreated on subsequent generations. This trades repeated initialization work for lower simultaneous GPU residency; the download size is not a VRAM requirement.
|
| 128 |
-
|
| 129 |
-
WebAssembly threading is set to one. The application does not require cross-origin isolation headers for WASM threads. Serve it over HTTPS or localhost so the browser can expose WebGPU; opening `index.html` directly as a local file is not the supported launch method. See [ONNX Runtime's environment options](https://onnxruntime.ai/docs/tutorials/web/env-flags-and-session-options.html).
|
| 130 |
-
|
| 131 |
-
The loader streams model downloads, checks file lengths, and stores assets in the browser's Cache API when available. A build-generated SHA-256 revision identifies the asset set and versions cache requests. Concurrent requests share the same pending model load; failed downloads can be retried, and truncated cached files are removed so retries can recover. Storage denial or quota errors do not prevent an uncached download.
|
| 132 |
-
|
| 133 |
-
Cache availability depends on browser policy, private browsing, available disk space, and eviction. Cached model files can avoid repeated network transfers, but they do not remove session initialization costs. No service worker is registered, and a fully offline page reload is not guaranteed.
|
| 134 |
-
|
| 135 |
-
## Files and integration
|
| 136 |
-
|
| 137 |
-
The exported model package contains:
|
| 138 |
-
|
| 139 |
-
```text
|
| 140 |
-
README.md
|
| 141 |
-
NDLGO2k.png
|
| 142 |
-
manifest.json
|
| 143 |
-
text_encoder_q4.onnx
|
| 144 |
-
text_encoder_q4.onnx.data
|
| 145 |
-
unet_mixed_q4q8.onnx
|
| 146 |
-
unet_mixed_q4q8.onnx.data
|
| 147 |
-
vae_decoder_fp16.onnx
|
| 148 |
-
vae_decoder_fp16.onnx.data
|
| 149 |
-
tokenizer/
|
| 150 |
-
tokenizer.json
|
| 151 |
-
tokenizer_config.json
|
| 152 |
-
scheduler/
|
| 153 |
-
scheduler_config.json
|
| 154 |
-
```
|
| 155 |
-
|
| 156 |
-
Keep each `.onnx` file together with its matching `.onnx.data` file. The ONNX graphs contain external-weight references; uploading only the small graph files is insufficient. The runtime reads scheduler coefficients from `manifest.json`; the scheduler configuration is also retained in the exported package for reference.
|
| 157 |
-
|
| 158 |
-
The static build places the inference assets under `models/sdxs/` and adds `assets.json` with filenames, byte counts, and a revision. The local-folder loader validates the manifest, required files, and fixed tensor shapes before accepting a package.
|
| 159 |
-
|
| 160 |
-
The graphs require the accompanying browser orchestration or an equivalent implementation of tokenization, tensor conversion, sampling, and decoding. This ONNX package is not a standard Diffusers `from_pretrained()` directory, and no hosted Hugging Face Inference API endpoint is configured.
|
| 161 |
-
|
| 162 |
-
## Run and host
|
| 163 |
-
|
| 164 |
-
From the NanoDiffuser source checkout, use Node.js **22.13.0 or newer**:
|
| 165 |
-
|
| 166 |
-
```bash
|
| 167 |
-
cd test-site
|
| 168 |
-
npm ci
|
| 169 |
-
npm run dev:demo
|
| 170 |
-
```
|
| 171 |
-
|
| 172 |
-
The development address is `http://127.0.0.1:5173/`. To build and preview the static release:
|
| 173 |
-
|
| 174 |
-
```bash
|
| 175 |
-
npm run build:demo
|
| 176 |
-
npm run preview:demo
|
| 177 |
-
```
|
| 178 |
-
|
| 179 |
-
The preview address is `http://127.0.0.1:4173/`. The output directory is `test-site/out`. The build copies the supplied `sdxs512_webgpu` weights, tokenizer, and manifest and packages the required browser runtime assets.
|
| 180 |
-
|
| 181 |
-
To prepare an upload folder:
|
| 182 |
-
|
| 183 |
-
```bash
|
| 184 |
-
npm run package:hf
|
| 185 |
-
```
|
| 186 |
-
|
| 187 |
-
Create a Hugging Face **Static Space** and upload the **contents** of the generated `hf-space` folder to its root, retaining the directory structure:
|
| 188 |
-
|
| 189 |
-
```text
|
| 190 |
-
README.md
|
| 191 |
-
index.html
|
| 192 |
-
logo.png
|
| 193 |
-
assets/
|
| 194 |
-
models/
|
| 195 |
-
```
|
| 196 |
-
|
| 197 |
-
For this prebuilt upload, the Space's `README.md` only needs its deployment metadata; this model card contains the full documentation:
|
| 198 |
-
|
| 199 |
-
```yaml
|
| 200 |
-
---
|
| 201 |
-
title: NanoDiffuser
|
| 202 |
-
emoji: ◻️
|
| 203 |
-
colorFrom: gray
|
| 204 |
-
colorTo: gray
|
| 205 |
-
sdk: static
|
| 206 |
-
app_file: index.html
|
| 207 |
-
fullWidth: true
|
| 208 |
-
header: mini
|
| 209 |
-
short_description: Generate images locally with NanoDiffuser and WebGPU.
|
| 210 |
-
---
|
| 211 |
-
```
|
| 212 |
-
|
| 213 |
-
A prebuilt upload needs no build command or server dependencies. The included weights are served with the application, so this deployment does not need a separate remote model download integration. Upload real weight files, including large `.onnx.data` files, rather than unresolved Git LFS pointer text.
|
| 214 |
-
|
| 215 |
-
If building on Hugging Face from source instead, upload the source tree and `sdxs512_webgpu` folder and use:
|
| 216 |
-
|
| 217 |
-
```yaml
|
| 218 |
-
sdk: static
|
| 219 |
-
app_build_command: cd test-site && npm ci && npm run build:demo
|
| 220 |
-
app_file: test-site/out/index.html
|
| 221 |
-
```
|
| 222 |
-
|
| 223 |
-
Exclude local dependencies and generated folders such as `node_modules`, `.next`, `.vinext`, `.wrangler`, `dist`, `out`, and `demo/public/models` from a source upload. Hugging Face documents both deployment approaches in its [Static Spaces guide](https://huggingface.co/docs/hub/spaces-sdks-static).
|
| 224 |
-
|
| 225 |
-
## Example prompts
|
| 226 |
-
|
| 227 |
-
The interface includes these five prompts. They are examples, not evidence of additional fine-tuning or measured benchmark performance.
|
| 228 |
-
|
| 229 |
-
**Sunlit garden**
|
| 230 |
-
|
| 231 |
-
> sunlit garden full of blooming flowers, stone path, small pond reflecting the sky, warm afternoon light, impressionist painting, oil on canvas, visible brushstrokes, soft broken color, luminous atmosphere, delicate painterly texture, elegant composition
|
| 232 |
-
|
| 233 |
-
**Rainy Paris**
|
| 234 |
-
|
| 235 |
-
> rainy Paris street, pedestrians with umbrellas, reflections on wet pavement, glowing shop windows, soft evening light, impressionist painting, loose brushwork, atmospheric perspective, oil on canvas, muted colors, romantic mood, painterly texture
|
| 236 |
-
|
| 237 |
-
**Wildflower meadow**
|
| 238 |
-
|
| 239 |
-
> woman walking through a meadow of wildflowers, flowing dress, summer breeze, bright sunlight, impressionist painting, oil on canvas, visible brushstrokes, luminous color, soft edges, natural light, poetic atmosphere
|
| 240 |
-
|
| 241 |
-
**Flower bouquet**
|
| 242 |
-
|
| 243 |
-
> bouquet of wildflowers in a ceramic vase, scattered petals on a wooden table, warm window light, impressionist still life painting, oil on canvas, visible brushstrokes, rich painterly texture, beautiful color harmony
|
| 244 |
-
|
| 245 |
-
**Hillside village**
|
| 246 |
-
|
| 247 |
-
> old European village on a hillside, stone cottages, narrow road, flowering trees, distant mountains, golden evening light, impressionist painting, oil on canvas, textured brushwork, warm colors, atmospheric perspective, timeless pastoral mood
|
| 248 |
-
|
| 249 |
-
## Performance and validation
|
| 250 |
-
|
| 251 |
-
One denoising step reduces the number of U-Net evaluations. It does not specify a universal generation time. Download speed, GPU/driver/browser support, session creation, shader compilation, quantized operators, and decoding all affect latency. The displayed elapsed time starts when inference is requested after the model-file loader completes and includes session setup; it excludes the preceding model-file download.
|
| 252 |
-
|
| 253 |
-
No NanoDiffuser-specific FPS, minimum VRAM, or cross-device performance benchmark is claimed here. Performance measurements from other implementations do not establish the speed of this browser package.
|
| 254 |
-
|
| 255 |
-
The exporter includes ONNX structural checks, quantization checks, finite-output checks, and reference comparisons with minimum cosine similarity gates of **0.96 for CLIP embeddings** and **0.90 for U-Net predictions**. Those thresholds are export acceptance criteria, not reported benchmark scores. The probes use a small reference case and do not establish general visual quality.
|
| 256 |
-
|
| 257 |
-
The implementation work included successful builds, type/lint checks, and loader checks for shared downloads, cache reuse, retry recovery, corrupted cached files, and unavailable browser storage. An end-to-end GPU generation run and a broad device compatibility matrix were not performed as part of that verification.
|
| 258 |
-
|
| 259 |
-
## Privacy, intended use, and limitations
|
| 260 |
-
|
| 261 |
-
NanoDiffuser is intended for creative image generation, local experimentation, and browser inference development. Prompts, selected local model files, and generated pixels are processed on the user's device. The application has no prompt/image upload endpoint. Network requests fetch the application and model assets; normal hosting request logs can still exist. Browser model caching does not imply server-side storage of generated images.
|
| 262 |
-
|
| 263 |
-
The interface states:
|
| 264 |
-
|
| 265 |
-
> Images are AI-generated on your device and never uploaded. You’re responsible for what you create.
|
| 266 |
-
|
| 267 |
-
Outputs can contain artifacts, inaccurate text, distorted objects, or imperfect prompt adherence. Quantization and the tiny decoder can change the output relative to higher-precision implementations. Inherited biases and limitations remain relevant, and this conversion does not add a content-safety classifier or moderation service. Review outputs before sharing or using them, and comply with the applicable licenses and rights of others.
|
| 268 |
-
|
| 269 |
-
## License
|
| 270 |
-
|
| 271 |
-
The converted weights retain their inherited **OpenRAIL++** licensing requirements. Preserve applicable license and attribution notices when redistributing the files. Software dependencies retain their respective licenses.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|