Instructions to use highscoregames12018/ComfyUI_Files with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use highscoregames12018/ComfyUI_Files with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("TheRaf7/ultra-real-wan2.2", dtype=torch.bfloat16, device_map="cuda") pipe.load_lora_weights("highscoregames12018/ComfyUI_Files") prompt = "-" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
| # (c) City96 || Apache-2.0 (apache.org/licenses/LICENSE-2.0) | |
| import warnings | |
| import logging | |
| import torch | |
| import gguf | |
| import re | |
| import os | |
| from .ops import GGMLTensor | |
| from .dequant import is_quantized, dequantize_tensor | |
| IMG_ARCH_LIST = {"flux", "sd1", "sdxl", "sd3", "aura", "hidream", "cosmos", "ltxv", "hyvid", "wan", "lumina2", "qwen_image"} | |
| TXT_ARCH_LIST = {"t5", "t5encoder", "llama", "qwen2vl", "qwen3", "qwen3vl", "gemma3"} | |
| VIS_TYPE_LIST = {"clip-vision", "mmproj"} | |
| def get_orig_shape(reader, tensor_name): | |
| field_key = f"comfy.gguf.orig_shape.{tensor_name}" | |
| field = reader.get_field(field_key) | |
| if field is None: | |
| return None | |
| # Has original shape metadata, so we try to decode it. | |
| if len(field.types) != 2 or field.types[0] != gguf.GGUFValueType.ARRAY or field.types[1] != gguf.GGUFValueType.INT32: | |
| raise TypeError(f"Bad original shape metadata for {field_key}: Expected ARRAY of INT32, got {field.types}") | |
| return torch.Size(tuple(int(field.parts[part_idx][0]) for part_idx in field.data)) | |
| def get_field(reader, field_name, field_type): | |
| field = reader.get_field(field_name) | |
| if field is None: | |
| return None | |
| elif field_type == str: | |
| # extra check here as this is used for checking arch string | |
| if len(field.types) != 1 or field.types[0] != gguf.GGUFValueType.STRING: | |
| raise TypeError(f"Bad type for GGUF {field_name} key: expected string, got {field.types!r}") | |
| return str(field.parts[field.data[-1]], encoding="utf-8") | |
| elif field_type in [int, float, bool]: | |
| return field_type(field.parts[field.data[-1]].item()) | |
| else: | |
| raise TypeError(f"Unknown field type {field_type}") | |
| def get_list_field(reader, field_name, field_type): | |
| field = reader.get_field(field_name) | |
| if field is None: | |
| return None | |
| elif field_type == str: | |
| return tuple(str(field.parts[part_idx], encoding="utf-8") for part_idx in field.data) | |
| elif field_type in [int, float, bool]: | |
| return tuple(field_type(field.parts[part_idx][0]) for part_idx in field.data) | |
| else: | |
| raise TypeError(f"Unknown field type {field_type}") | |
| def get_gguf_metadata(reader): | |
| """Extract all simple metadata fields like safetensors""" | |
| metadata = {} | |
| for field_name in reader.fields: | |
| try: | |
| field = reader.get_field(field_name) | |
| if len(field.types) == 1: # Simple scalar fields only | |
| if field.types[0] == gguf.GGUFValueType.STRING: | |
| metadata[field_name] = str(field.parts[field.data[-1]], "utf-8") | |
| elif field.types[0] == gguf.GGUFValueType.INT32: | |
| metadata[field_name] = int(field.parts[field.data[-1]]) | |
| elif field.types[0] == gguf.GGUFValueType.F32: | |
| metadata[field_name] = float(field.parts[field.data[-1]]) | |
| elif field.types[0] == gguf.GGUFValueType.BOOL: | |
| metadata[field_name] = bool(field.parts[field.data[-1]]) | |
| except: | |
| continue | |
| return metadata | |
| def gguf_sd_loader(path, handle_prefix="model.diffusion_model.", is_text_model=False): | |
| """ | |
| Read state dict as fake tensors | |
| """ | |
| reader = gguf.GGUFReader(path) | |
| # filter and strip prefix | |
| has_prefix = False | |
| if handle_prefix is not None: | |
| prefix_len = len(handle_prefix) | |
| tensor_names = set(tensor.name for tensor in reader.tensors) | |
| has_prefix = any(s.startswith(handle_prefix) for s in tensor_names) | |
| tensors = [] | |
| for tensor in reader.tensors: | |
| sd_key = tensor_name = tensor.name | |
| if has_prefix: | |
| if not tensor_name.startswith(handle_prefix): | |
| continue | |
| sd_key = tensor_name[prefix_len:] | |
| tensors.append((sd_key, tensor)) | |
| # detect and verify architecture | |
| compat = None | |
| arch_str = get_field(reader, "general.architecture", str) | |
| type_str = get_field(reader, "general.type", str) | |
| if arch_str in [None, "pig", "cow"]: | |
| if is_text_model: | |
| raise ValueError(f"This gguf file is incompatible with llama.cpp!\nConsider using safetensors or a compatible gguf file\n({path})") | |
| compat = "sd.cpp" if arch_str is None else arch_str | |
| # import here to avoid changes to convert.py breaking regular models | |
| from .tools.convert import detect_arch | |
| try: | |
| arch_str = detect_arch(set(val[0] for val in tensors)).arch | |
| except Exception as e: | |
| raise ValueError(f"This model is not currently supported - ({e})") | |
| elif arch_str not in TXT_ARCH_LIST and is_text_model: | |
| if type_str not in VIS_TYPE_LIST: | |
| raise ValueError(f"Unexpected text model architecture type in GGUF file: {arch_str!r}") | |
| elif arch_str not in IMG_ARCH_LIST and not is_text_model: | |
| raise ValueError(f"Unexpected architecture type in GGUF file: {arch_str!r}") | |
| if compat: | |
| logging.warning(f"Warning: This gguf model file is loaded in compatibility mode '{compat}' [arch:{arch_str}]") | |
| # main loading loop | |
| state_dict = {} | |
| qtype_dict = {} | |
| for sd_key, tensor in tensors: | |
| tensor_name = tensor.name | |
| # torch_tensor = torch.from_numpy(tensor.data) # mmap | |
| # NOTE: line above replaced with this block to avoid persistent numpy warning about mmap | |
| with warnings.catch_warnings(): | |
| warnings.filterwarnings("ignore", message="The given NumPy array is not writable") | |
| torch_tensor = torch.from_numpy(tensor.data) # mmap | |
| shape = get_orig_shape(reader, tensor_name) | |
| if shape is None: | |
| shape = torch.Size(tuple(int(v) for v in reversed(tensor.shape))) | |
| # Workaround for stable-diffusion.cpp SDXL detection. | |
| if compat == "sd.cpp" and arch_str == "sdxl": | |
| if any([tensor_name.endswith(x) for x in (".proj_in.weight", ".proj_out.weight")]): | |
| while len(shape) > 2 and shape[-1] == 1: | |
| shape = shape[:-1] | |
| # add to state dict | |
| if tensor.tensor_type in {gguf.GGMLQuantizationType.F32, gguf.GGMLQuantizationType.F16}: | |
| torch_tensor = torch_tensor.view(*shape) | |
| state_dict[sd_key] = GGMLTensor(torch_tensor, tensor_type=tensor.tensor_type, tensor_shape=shape) | |
| # 1D tensors shouldn't be quantized, this is a fix for BF16 | |
| if len(shape) <= 1 and tensor.tensor_type == gguf.GGMLQuantizationType.BF16: | |
| state_dict[sd_key] = dequantize_tensor(state_dict[sd_key], dtype=torch.float32) | |
| # keep track of loaded tensor types | |
| tensor_type_str = getattr(tensor.tensor_type, "name", repr(tensor.tensor_type)) | |
| qtype_dict[tensor_type_str] = qtype_dict.get(tensor_type_str, 0) + 1 | |
| # print loaded tensor type counts | |
| logging.info("gguf qtypes: " + ", ".join(f"{k} ({v})" for k, v in qtype_dict.items())) | |
| # mark largest tensor for vram estimation | |
| qsd = {k:v for k,v in state_dict.items() if is_quantized(v)} | |
| if len(qsd) > 0: | |
| max_key = max(qsd.keys(), key=lambda k: qsd[k].numel()) | |
| state_dict[max_key].is_largest_weight = True | |
| # extra info to return | |
| extra = { | |
| "arch_str": arch_str, | |
| "metadata": get_gguf_metadata(reader) | |
| } | |
| return (state_dict, extra) | |
| # for remapping llama.cpp -> original key names | |
| T5_SD_MAP = { | |
| "enc.": "encoder.", | |
| ".blk.": ".block.", | |
| "token_embd": "shared", | |
| "output_norm": "final_layer_norm", | |
| "attn_q": "layer.0.SelfAttention.q", | |
| "attn_k": "layer.0.SelfAttention.k", | |
| "attn_v": "layer.0.SelfAttention.v", | |
| "attn_o": "layer.0.SelfAttention.o", | |
| "attn_norm": "layer.0.layer_norm", | |
| "attn_rel_b": "layer.0.SelfAttention.relative_attention_bias", | |
| "ffn_up": "layer.1.DenseReluDense.wi_1", | |
| "ffn_down": "layer.1.DenseReluDense.wo", | |
| "ffn_gate": "layer.1.DenseReluDense.wi_0", | |
| "ffn_norm": "layer.1.layer_norm", | |
| } | |
| LLAMA_SD_MAP = { | |
| "blk.": "model.layers.", | |
| "attn_norm": "input_layernorm", | |
| "attn_q_norm.": "self_attn.q_norm.", | |
| "attn_k_norm.": "self_attn.k_norm.", | |
| "attn_v_norm.": "self_attn.v_norm.", | |
| "attn_q": "self_attn.q_proj", | |
| "attn_k": "self_attn.k_proj", | |
| "attn_v": "self_attn.v_proj", | |
| "attn_output": "self_attn.o_proj", | |
| "ffn_up": "mlp.up_proj", | |
| "ffn_down": "mlp.down_proj", | |
| "ffn_gate": "mlp.gate_proj", | |
| "ffn_norm": "post_attention_layernorm", | |
| "token_embd": "model.embed_tokens", | |
| "output_norm": "model.norm", | |
| "output.weight": "lm_head.weight", | |
| } | |
| GEMMA3_SD_MAP = LLAMA_SD_MAP.copy() | |
| GEMMA3_SD_MAP.update({ | |
| "ffn_norm": "pre_feedforward_layernorm", | |
| "post_ffw_norm": "post_feedforward_layernorm", | |
| "post_attention_norm": "post_attention_layernorm", | |
| }) | |
| CLIP_VISION_SD_MAP = { | |
| "mm.": "visual.merger.mlp.", | |
| "v.post_ln.": "visual.merger.ln_q.", | |
| "v.patch_embd": "visual.patch_embed.proj", | |
| "v.blk.": "visual.blocks.", | |
| "ffn_up": "mlp.up_proj", | |
| "ffn_down": "mlp.down_proj", | |
| "ffn_gate": "mlp.gate_proj", | |
| "attn_out.": "attn.proj.", | |
| "ln1.": "norm1.", | |
| "ln2.": "norm2.", | |
| } | |
| def sd_map_replace(raw_sd, key_map): | |
| sd = {} | |
| for k,v in raw_sd.items(): | |
| for s,d in key_map.items(): | |
| k = k.replace(s,d) | |
| sd[k] = v | |
| return sd | |
| def llama_permute(raw_sd, n_head, n_head_kv): | |
| # Reverse version of LlamaModel.permute in llama.cpp convert script | |
| sd = {} | |
| permute = lambda x,h: x.reshape(h, x.shape[0] // h // 2, 2, *x.shape[1:]).swapaxes(1, 2).reshape(x.shape) | |
| for k,v in raw_sd.items(): | |
| if k.endswith(("q_proj.weight", "q_proj.bias")): | |
| v.data = permute(v.data, n_head) | |
| if k.endswith(("k_proj.weight", "k_proj.bias")): | |
| v.data = permute(v.data, n_head_kv) | |
| sd[k] = v | |
| return sd | |
| def gemma3_norm_corrections(sd): | |
| # Reverse change from Gemma3Model modify_tensors in llama.cpp convert script | |
| norm_patterns = [ | |
| "input_layernorm.weight", | |
| "post_attention_layernorm.weight", | |
| "pre_feedforward_layernorm.weight", | |
| "post_feedforward_layernorm.weight", | |
| "self_attn.q_norm.weight", | |
| "self_attn.k_norm.weight", | |
| "model.norm.weight" | |
| ] | |
| corrected = 0 | |
| for key in list(sd.keys()): | |
| if any(p in key for p in norm_patterns): | |
| if is_quantized(sd[key]): | |
| sd[key] = dequantize_tensor(sd[key], dtype=torch.float32) - 1.0 | |
| else: | |
| sd[key] = sd[key].float() - 1.0 | |
| corrected += 1 | |
| #logging.info(f"Gemma3: Applied -1 norm correction to {corrected} tensors") | |
| return sd | |
| def strip_quant_suffix(name): | |
| pattern = r"[-_]?(?:ud-)?i?q[0-9]_[a-z0-9_\-]{1,8}$" | |
| match = re.search(pattern, name, re.IGNORECASE) | |
| if match: | |
| name = name[:match.start()] | |
| return name | |
| def gguf_mmproj_loader(path): | |
| # Reverse version of Qwen2VLVisionModel.modify_tensors | |
| logging.info("Attenpting to find mmproj file for text encoder...") | |
| # get name to match w/o quant suffix | |
| tenc_fname = os.path.basename(path) | |
| tenc = os.path.splitext(tenc_fname)[0].lower() | |
| tenc = strip_quant_suffix(tenc) | |
| # try and find matching mmproj | |
| target = [] | |
| root = os.path.dirname(path) | |
| for fname in os.listdir(root): | |
| name, ext = os.path.splitext(fname) | |
| if ext.lower() != ".gguf": | |
| continue | |
| if "mmproj" not in name.lower(): | |
| continue | |
| if tenc in name.lower(): | |
| target.append(fname) | |
| if len(target) == 0: | |
| logging.error(f"Error: Can't find mmproj file for '{tenc_fname}' (matching:'{tenc}')! Qwen-Image-Edit will be broken!") | |
| return {} | |
| if len(target) > 1: | |
| logging.error(f"Ambiguous mmproj for text encoder '{tenc_fname}', will use first match.") | |
| logging.info(f"Using mmproj '{target[0]}' for text encoder '{tenc_fname}'.") | |
| target = os.path.join(root, target[0]) | |
| vsd, _ = gguf_sd_loader(target, is_text_model=True) | |
| # concat 4D to 5D | |
| if "v.patch_embd.weight.1" in vsd: | |
| w1 = dequantize_tensor(vsd.pop("v.patch_embd.weight"), dtype=torch.float32) | |
| w2 = dequantize_tensor(vsd.pop("v.patch_embd.weight.1"), dtype=torch.float32) | |
| vsd["v.patch_embd.weight"] = torch.stack([w1, w2], dim=2) | |
| # run main replacement | |
| vsd = sd_map_replace(vsd, CLIP_VISION_SD_MAP) | |
| # handle split Q/K/V | |
| if "visual.blocks.0.attn_q.weight" in vsd: | |
| attns = {} | |
| # filter out attentions + group | |
| for k,v in vsd.items(): | |
| if any(x in k for x in ["attn_q", "attn_k", "attn_v"]): | |
| k_attn, k_name = k.rsplit(".attn_", 1) | |
| k_attn += ".attn.qkv." + k_name.split(".")[-1] | |
| if k_attn not in attns: | |
| attns[k_attn] = {} | |
| attns[k_attn][k_name] = dequantize_tensor( | |
| v, dtype=(torch.bfloat16 if is_quantized(v) else torch.float16) | |
| ) | |
| # recombine | |
| for k,v in attns.items(): | |
| suffix = k.split(".")[-1] | |
| vsd[k] = torch.cat([ | |
| v[f"q.{suffix}"], | |
| v[f"k.{suffix}"], | |
| v[f"v.{suffix}"], | |
| ], dim=0) | |
| del attns | |
| return vsd | |
| def gguf_tokenizer_loader(path, temb_shape): | |
| # convert gguf tokenizer to spiece | |
| logging.info("Attempting to recreate sentencepiece tokenizer from GGUF file metadata...") | |
| try: | |
| from sentencepiece import sentencepiece_model_pb2 as model | |
| except ImportError: | |
| raise ImportError("Please make sure sentencepiece and protobuf are installed.\npip install sentencepiece protobuf") | |
| spm = model.ModelProto() | |
| reader = gguf.GGUFReader(path) | |
| if get_field(reader, "tokenizer.ggml.model", str) == "t5": | |
| if temb_shape == (256384, 4096): # probably UMT5 | |
| spm.trainer_spec.model_type == 1 # Unigram (do we have a T5 w/ BPE?) | |
| else: | |
| raise NotImplementedError("Unknown model, can't set tokenizer!") | |
| else: | |
| raise NotImplementedError("Unknown model, can't set tokenizer!") | |
| spm.normalizer_spec.add_dummy_prefix = get_field(reader, "tokenizer.ggml.add_space_prefix", bool) | |
| spm.normalizer_spec.remove_extra_whitespaces = get_field(reader, "tokenizer.ggml.remove_extra_whitespaces", bool) | |
| tokens = get_list_field(reader, "tokenizer.ggml.tokens", str) | |
| scores = get_list_field(reader, "tokenizer.ggml.scores", float) | |
| toktypes = get_list_field(reader, "tokenizer.ggml.token_type", int) | |
| for idx, (token, score, toktype) in enumerate(zip(tokens, scores, toktypes)): | |
| # # These aren't present in the original? | |
| # if toktype == 5 and idx >= temb_shape[0]%1000): | |
| # continue | |
| piece = spm.SentencePiece() | |
| piece.piece = token | |
| piece.score = score | |
| piece.type = toktype | |
| spm.pieces.append(piece) | |
| # unsure if any of these are correct | |
| spm.trainer_spec.byte_fallback = True | |
| spm.trainer_spec.vocab_size = len(tokens) # split off unused? | |
| spm.trainer_spec.max_sentence_length = 4096 | |
| spm.trainer_spec.eos_id = get_field(reader, "tokenizer.ggml.eos_token_id", int) | |
| spm.trainer_spec.pad_id = get_field(reader, "tokenizer.ggml.padding_token_id", int) | |
| logging.info(f"Created tokenizer with vocab size of {len(spm.pieces)}") | |
| del reader | |
| return torch.ByteTensor(list(spm.SerializeToString())) | |
| def gguf_tekken_tokenizer_loader(path, temb_shape): | |
| # convert ggml (hf) tokenizer metadata to tekken/comfy data | |
| logging.info("Attempting to recreate tekken tokenizer from GGUF file metadata...") | |
| import json | |
| import base64 | |
| from transformers.convert_slow_tokenizer import bytes_to_unicode | |
| reader = gguf.GGUFReader(path) | |
| model_str = get_field(reader, "tokenizer.ggml.model", str) | |
| if model_str == "gpt2": | |
| if temb_shape == (131072, 5120): # probably Mistral | |
| data = { | |
| "config": {"num_vocab_tokens": 150000, "default_vocab_size": 131072}, | |
| "vocab": [], | |
| "special_tokens": [], | |
| } | |
| else: | |
| raise NotImplementedError("Unknown model, can't set tokenizer!") | |
| else: | |
| raise NotImplementedError("Unknown model, can't set tokenizer!") | |
| tokens = get_list_field(reader, "tokenizer.ggml.tokens", str) | |
| toktypes = get_list_field(reader, "tokenizer.ggml.token_type", int) | |
| decoder = {v: k for k, v in bytes_to_unicode().items()} | |
| for idx, (token, toktype) in enumerate(zip(tokens, toktypes)): | |
| if toktype == 3: | |
| data["special_tokens"].append( | |
| {'rank': idx, 'token_str': token, 'is_control': True} | |
| ) | |
| else: | |
| tok = bytes([decoder[char] for char in token]) | |
| data["vocab"].append({ | |
| "rank": len(data["vocab"]), | |
| "token_bytes": base64.b64encode(tok).decode("ascii"), | |
| "token_str": tok.decode("utf-8", errors="replace") # ? | |
| }) | |
| logging.info(f"Created tekken tokenizer with vocab size of {len(data['vocab'])} (+{len(data['special_tokens'])})") | |
| del reader | |
| return torch.ByteTensor(list(json.dumps(data).encode('utf-8'))) | |
| def gguf_gemma3_tokenizer_loader(path): | |
| #TODO: merge into gguf_tokenizer_loader | |
| logging.info("Attempting to recreate sentencepiece tokenizer from GGUF file metadata...") | |
| try: | |
| from sentencepiece import sentencepiece_model_pb2 as model | |
| except ImportError: | |
| raise ImportError("Please install sentencepiece and protobuf.\npip install sentencepiece protobuf") | |
| spm = model.ModelProto() | |
| reader = gguf.GGUFReader(path) | |
| spm.normalizer_spec.name = "identity" | |
| spm.normalizer_spec.add_dummy_prefix = False | |
| spm.trainer_spec.model_type = 2 | |
| spm.trainer_spec.input_format = "tsv" | |
| spm.trainer_spec.byte_fallback = True | |
| spm.trainer_spec.max_sentence_length = 4192 | |
| spm.trainer_spec.bos_piece = "<bos>" | |
| tokens = get_list_field(reader, "tokenizer.ggml.tokens", str) | |
| scores = get_list_field(reader, "tokenizer.ggml.scores", float) | |
| toktype = get_list_field(reader, "tokenizer.ggml.token_type", int) | |
| if not tokens or not scores or not toktype: | |
| raise ValueError("Missing tokenizer metadata") | |
| for idx in range(len(tokens)): | |
| piece = spm.SentencePiece() | |
| piece.piece = tokens[idx] | |
| if idx == 3: # UNK position | |
| piece.type = 2 # UNK Token | |
| piece.score = 0.0 # UNK Score | |
| else: | |
| piece.type = toktype[idx] | |
| piece.score = scores[idx] | |
| spm.pieces.append(piece) | |
| spm.trainer_spec.vocab_size = len(spm.pieces) | |
| logging.info(f"Created tokenizer with vocab size of {len(spm.pieces)}") | |
| del reader | |
| return torch.ByteTensor(list(spm.SerializeToString())) | |
| def gguf_clip_loader(path): | |
| sd, extra = gguf_sd_loader(path, is_text_model=True) | |
| arch = extra.get("arch_str", None) | |
| if arch in {"t5", "t5encoder"}: | |
| temb_key = "token_embd.weight" | |
| if temb_key in sd and sd[temb_key].shape == (256384, 4096): | |
| # non-standard Comfy-Org tokenizer | |
| sd["spiece_model"] = gguf_tokenizer_loader(path, sd[temb_key].shape) | |
| # TODO: dequantizing token embed here is janky but otherwise we OOM due to tensor being massive. | |
| logging.warning(f"Dequantizing {temb_key} to prevent runtime OOM.") | |
| sd[temb_key] = dequantize_tensor(sd[temb_key], dtype=torch.float16) | |
| sd = sd_map_replace(sd, T5_SD_MAP) | |
| elif arch in {"llama", "qwen2vl", "qwen3", "qwen3vl", "gemma3"}: | |
| # TODO: pass model_options["vocab_size"] to loader somehow | |
| temb_key = "token_embd.weight" | |
| if temb_key in sd and sd[temb_key].shape[0] >= (64 * 1024): | |
| if arch == "llama" and sd[temb_key].shape == (131072, 5120): | |
| # non-standard Comfy-Org tokenizer | |
| sd["tekken_model"] = gguf_tekken_tokenizer_loader(path, sd[temb_key].shape) | |
| elif arch == "gemma3": | |
| sd["spiece_model"] = gguf_gemma3_tokenizer_loader(path) | |
| # See note above for T5. | |
| logging.warning(f"Dequantizing {temb_key} to prevent runtime OOM.") | |
| sd[temb_key] = dequantize_tensor(sd[temb_key], dtype=torch.float16) | |
| if arch == "gemma3": | |
| sd = sd_map_replace(sd, GEMMA3_SD_MAP) | |
| sd = gemma3_norm_corrections(sd) | |
| else: | |
| sd = sd_map_replace(sd, LLAMA_SD_MAP) | |
| if arch == "llama": | |
| sd = llama_permute(sd, 32, 8) # L3 / Mistral | |
| if arch == "qwen2vl": | |
| vsd = gguf_mmproj_loader(path) | |
| sd.update(vsd) | |
| else: | |
| pass | |
| return sd | |